From c6b2a206b6a27bd136adb856cf51828bf91d892d Mon Sep 17 00:00:00 2001 From: John DeNisco Date: Wed, 1 Nov 2017 12:37:47 -0400 Subject: A bit of cleanup, updated the README, started vhost test. Change-Id: I49b998644b8b79c778c1186fc09831b1cd8fc015 Signed-off-by: John DeNisco --- extras/vpp_config/README.rst | 6 +- extras/vpp_config/setup.py | 4 +- extras/vpp_config/vpp_config.py | 18 +++- extras/vpp_config/vpplib/AutoConfig.py | 153 +++++++++++++++++++++++++++----- extras/vpp_config/vpplib/VPPUtil.py | 90 ++++++++++++++++++- extras/vpp_config/vpplib/VppGrubUtil.py | 9 +- extras/vpp_config/vpplib/VppPCIUtil.py | 7 +- 7 files changed, 245 insertions(+), 42 deletions(-) (limited to 'extras/vpp_config') diff --git a/extras/vpp_config/README.rst b/extras/vpp_config/README.rst index 748f66f7c0e..ec9c8e46cf4 100644 --- a/extras/vpp_config/README.rst +++ b/extras/vpp_config/README.rst @@ -5,7 +5,7 @@ VPP in a simple and safe manner. The utility takes input from the user and then modifies the key configuration files. The user can then examine these files to be sure they are correct and then actually apply the configuration. The user can also install a released and stable version of VPP. This is currently -released with release 17.07. +released with release 17.10. Use: @@ -23,9 +23,7 @@ apply the configuration and then inspect the system again and then repeat. Caveats: -- Only supports Ubuntu, centos7 is coming shortly. -- When Inspecting the system, you may see a Huge page error, inspect the system a -few more times, if the error persists it is real. +- Supports Ubuntu, centos7, RedHat is coming shortly. For Developers: diff --git a/extras/vpp_config/setup.py b/extras/vpp_config/setup.py index 8d2a3968762..32c376d295c 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.3", + version="17.10.5", author="John DeNisco", author_email="jdenisco@cisco.com", description="VPP Configuration Utility", @@ -9,7 +9,7 @@ setup(name="vpp_config", keywords="vppconfig", url = 'https://wiki.fd.io/view/VPP', py_modules=['vpp_config'], - install_requires=['pyyaml'], + install_requires=['pyyaml','netaddr'], packages=['vpplib'], scripts=['scripts/vpp-config'], data_files=[('vpp/vpp-config/scripts', ['scripts/dpdk-devbind.py']), diff --git a/extras/vpp_config/vpp_config.py b/extras/vpp_config/vpp_config.py index b8b49a032b4..da455c178a4 100755 --- a/extras/vpp_config/vpp_config.py +++ b/extras/vpp_config/vpp_config.py @@ -450,6 +450,16 @@ def autoconfig_ipv4_setup(): acfg.ipv4_interface_setup() +def autoconfig_create_vm(): + """ + Setup IPv4 interfaces + + """ + + acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) + acfg.create_and_bridge_virtual_interfaces() + + def autoconfig_not_implemented(): """ This feature is not implemented @@ -465,10 +475,14 @@ def autoconfig_basic_test_menu(): """ +# 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.' - print "{}".format(basic_menu_text) input_valid = False @@ -506,6 +520,8 @@ def autoconfig_basic_test(): answer = autoconfig_basic_test_menu() if answer == '1': autoconfig_ipv4_setup() + # elif answer == '2': + # autoconfig_create_vm() elif answer == '9' or answer == 'q': return else: diff --git a/extras/vpp_config/vpplib/AutoConfig.py b/extras/vpp_config/vpplib/AutoConfig.py index ab943e0ef21..7b7d7a7be8f 100644 --- a/extras/vpp_config/vpplib/AutoConfig.py +++ b/extras/vpp_config/vpplib/AutoConfig.py @@ -17,7 +17,7 @@ import logging import os import re import yaml -import ipaddress +from netaddr import IPAddress from vpplib.VPPUtil import VPPUtil from vpplib.VppPCIUtil import VppPCIUtil @@ -90,30 +90,25 @@ class AutoConfig(object): Asks the user for a number within a range. default is returned if return is entered. - :returns: IP address and prefix len - :rtype: tuple + :returns: IP address with cidr + :rtype: str """ while True: - answer = raw_input("Please enter the IPv4 Address [n.n.n.n]: ") + answer = raw_input("Please enter the IPv4 Address [n.n.n.n/n]: ") try: - ipaddr = ipaddress.ip_address(u'{}'.format(answer)) + ipinput = answer.split('/') + ipaddr = IPAddress(ipinput[0]) + if len(ipinput) > 1: + plen = answer.split('/')[1] + else: + answer = raw_input("Please enter the netmask [n.n.n.n]: ") + plen = IPAddress(answer).netmask_bits() + return '{}/{}'.format(ipaddr, plen) except: print "Please enter a valid IPv4 address." continue - answer = raw_input("Please enter the netmask [n.n.n.n]: ") - try: - netmask = ipaddress.ip_address(u'{}'.format(answer)) - pl = ipaddress.ip_network(u'0.0.0.0/{}'.format(netmask)) - plen = pl.exploded.split('/')[1] - break - except: - print "Please enter a valid IPv4 address and netmask." - continue - - return ipaddr, plen - @staticmethod def _ask_user_range(question, first, last, default): """ @@ -1479,14 +1474,13 @@ other than VPP? [0-{}][0]? '.format(str(max_other_cores)) if name == 'local0': continue - question = "Would you like an address to interface {} [Y/n]? ".format(name) + question = "Would you like add address to interface {} [Y/n]? ".format(name) answer = self._ask_user_yn(question, 'y') if answer == 'y': address = {} - addr, plen = self._ask_user_ipv4() + addr = self._ask_user_ipv4() address['name'] = name address['addr'] = addr - address['plen'] = plen interfaces_with_ip.append(address) return interfaces_with_ip @@ -1527,8 +1521,7 @@ other than VPP? [0-{}][0]? '.format(str(max_other_cores)) for ints in ints_with_addrs: name = ints['name'] addr = ints['addr'] - plen = ints['plen'] - setipstr = 'set int ip address {} {}/{}\n'.format(name, addr, plen) + setipstr = 'set int ip address {} {}\n'.format(name, addr) setintupstr = 'set int state {} up\n'.format(name) content += setipstr + setintupstr @@ -1546,4 +1539,118 @@ other than VPP? [0-{}][0]? '.format(str(max_other_cores)) print("\nA script as been created at {}".format(filename)) print("This script can be run using the following:") - print("vppctl exec {}\n".format(filename)) \ No newline at end of file + print("vppctl exec {}\n".format(filename)) + + def _create_vints_questions(self, node): + """ + Ask the user some questions and get a list of interfaces + and IPv4 addresses associated with those interfaces + + :param node: Node dictionary. + :type node: dict + :returns: A list or interfaces with ip addresses + :rtype: list + """ + + vpputl = VPPUtil() + interfaces = vpputl.get_hardware(node) + if interfaces == {}: + return [] + + # First delete all the Virtual interfaces + for intf in sorted(interfaces.items()): + name = intf[0] + if name[:7] == 'Virtual': + cmd = 'vppctl delete vhost-user {}'.format(name) + (ret, stdout, stderr) = vpputl.exec_command(cmd) + if ret != 0: + logging.debug('{} failed on node {} {}'.format( + cmd, node['host'], stderr)) + + # Create a virtual interface, for each interface the user wants to use + interfaces = vpputl.get_hardware(node) + if interfaces == {}: + return [] + interfaces_with_virtual_interfaces = [] + inum = 1 + for intf in sorted(interfaces.items()): + name = intf[0] + if name == 'local0': + continue + + question = "Would you like connect this interface {} to the VM [Y/n]? ".format(name) + answer = self._ask_user_yn(question, 'y') + if answer == 'y': + sockfilename = '/tmp/sock{}.sock'.format(inum) + if os.path.exists(sockfilename): + os.remove(sockfilename) + cmd = 'vppctl create vhost-user socket {} server'.format(sockfilename) + (ret, stdout, stderr) = vpputl.exec_command(cmd) + if ret != 0: + raise RuntimeError("Create vhost failed on node {} {}." + .format(node['host'], stderr)) + vintname = stdout.rstrip('\r\n') + + interface = {'name': name, 'virtualinterface': '{}'.format(vintname), + 'bridge': '{}'.format(inum)} + inum += 1 + interfaces_with_virtual_interfaces.append(interface) + + return interfaces_with_virtual_interfaces + + def create_and_bridge_virtual_interfaces(self): + """ + After asking the user some questions, create a VM and connect the interfaces + to VPP interfaces + + """ + + for i in self._nodes.items(): + node = i[1] + + # Show the current bridge and interface configuration + print "\nThis the current bridge configuration:" + VPPUtil.show_bridge(node) + question = "\nWould you like to keep this configuration [Y/n]? " + answer = self._ask_user_yn(question, 'y') + if answer == 'y': + continue + + # Create a script that builds a bridge configuration with physical interfaces + # and virtual interfaces + ints_with_vints = self._create_vints_questions(node) + content = '' + for intf in ints_with_vints: + vhoststr = 'comment { The following command creates the socket }\n' + vhoststr += 'comment { and returns a virtual interface }\n' + vhoststr += 'comment {{ create vhost-user socket /tmp/sock{}.sock server }}\n'. \ + format(intf['bridge']) + + setintdnstr = 'set interface state {} down\n'.format(intf['name']) + + setintbrstr = 'set interface l2 bridge {} {}\n'.format(intf['name'], intf['bridge']) + setvintbrstr = 'set interface l2 bridge {} {}\n'.format(intf['virtualinterface'], intf['bridge']) + + # set interface state VirtualEthernet/0/0/0 up + setintvststr = 'set interface state {} up\n'.format(intf['virtualinterface']) + + # set interface state VirtualEthernet/0/0/0 down + setintupstr = 'set interface state {} up\n'.format(intf['name']) + + content += vhoststr + setintdnstr + setintbrstr + setvintbrstr + setintvststr + setintupstr + + # Write the content to the script + rootdir = node['rootdir'] + filename = rootdir + '/vpp/vpp-config/scripts/create_vms_and_connect_to_vpp' + with open(filename, 'w+') as sfile: + sfile.write(content) + + # Execute the script + cmd = 'vppctl exec {}'.format(filename) + (ret, stdout, stderr) = VPPUtil.exec_command(cmd) + if ret != 0: + logging.debug(stderr) + + print("\nA script as been created at {}".format(filename)) + print("This script can be run using the following:") + print("vppctl exec {}\n".format(filename)) diff --git a/extras/vpp_config/vpplib/VPPUtil.py b/extras/vpp_config/vpplib/VPPUtil.py index f042e80bd8f..4551cf45a5e 100644 --- a/extras/vpp_config/vpplib/VPPUtil.py +++ b/extras/vpp_config/vpplib/VPPUtil.py @@ -140,8 +140,11 @@ class VPPUtil(object): stderr)) reps = 'deb [trusted=yes] https://nexus.fd.io/content/' - reps += 'repositories/fd.io.stable.{}.ubuntu.{}.main/ ./\n' \ - .format(fdio_release, ubuntu_version) + # When using a stable branch + # reps += 'repositories/fd.io.stable.{}.ubuntu.{}.main/ ./\n' \ + # .format(fdio_release, ubuntu_version) + reps += 'repositories/fd.io.ubuntu.{}.main/ ./\n' \ + .format(ubuntu_version) cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile) (ret, stdout, stderr) = self.exec_command(cmd) @@ -201,8 +204,11 @@ class VPPUtil(object): reps = '[fdio-stable-{}]\n'.format(fdio_release) reps += 'name=fd.io stable/{} branch latest merge\n'.format(fdio_release) - reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.stable.{}.{}/\n'.\ - format(fdio_release, centos_version) + # When using stable + # reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.stable.{}.{}/\n'.\ + # format(fdio_release, centos_version) + reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.{}/\n'.\ + format(centos_version) reps += 'enabled=1\n' reps += 'gpgcheck=0' @@ -336,6 +342,10 @@ class VPPUtil(object): :param node: Node dictionary with cpuinfo. :type node: dict """ + + # First stop VPP + self.stop(node) + distro = self.get_linux_distro() if distro[0] == 'Ubuntu': self._uninstall_vpp_ubuntu(node) @@ -372,6 +382,23 @@ class VPPUtil(object): for _, value in def_setting_tb_displayed.items(): self.exec_command('vppctl sh {}'.format(value)) + @staticmethod + def get_vms(node): + """ + Get a list of VMs that are connected to VPP interfaces + + :param node: VPP node. + :type node: dict + :returns: Dictionary containing a list of VMs and the interfaces that are connected to VPP + :rtype: dictionary + """ + + vmdict = {} + + print "Need to implement get vms" + + return vmdict + @staticmethod def get_int_ip(node): """ @@ -393,6 +420,7 @@ class VPPUtil(object): if lines[0].split(' ')[0] == 'FileNotFoundError': return interfaces + name = '' for line in lines: if len(line) is 0: continue @@ -582,6 +610,23 @@ class VPPUtil(object): return 0 + @staticmethod + def restart(node): + """ + + Starts vpp for a given node + + :param node: VPP node. + :type node: dict + """ + + cmd = 'service vpp restart' + (ret, stdout, stderr) = VPPUtil.exec_command(cmd) + if ret != 0: + raise RuntimeError('{} failed on node {} {} {}'. + format(cmd, node['host'], + stdout, stderr)) + @staticmethod def start(node): """ @@ -699,3 +744,40 @@ class VPPUtil(object): version[dct[0]] = dct[1].lstrip(' ') return version + + @staticmethod + def show_bridge(node): + """ + Shows the current bridge configuration + + :param node: VPP node. + :type node: dict + """ + + cmd = 'vppctl show bridge' + (ret, stdout, stderr) = VPPUtil.exec_command(cmd) + if ret != 0: + raise RuntimeError('{} failed on node {} {} {}'. + format(cmd, node['host'], + stdout, stderr)) + lines = stdout.split('\r\n') + bridges = [] + for line in lines: + if line == 'no bridge-domains in use': + print line + return + if len(line) == 0: + continue + + lspl = line.lstrip(' ').split() + if lspl[0] != 'BD-ID': + bridges.append(lspl[0]) + + for bridge in bridges: + cmd = 'vppctl show bridge {} detail'.format(bridge) + (ret, stdout, stderr) = VPPUtil.exec_command(cmd) + if ret != 0: + raise RuntimeError('{} failed on node {} {} {}'. + format(cmd, node['host'], + stdout, stderr)) + print stdout diff --git a/extras/vpp_config/vpplib/VppGrubUtil.py b/extras/vpp_config/vpplib/VppGrubUtil.py index 4aac427c22a..1723170649e 100644 --- a/extras/vpp_config/vpplib/VppGrubUtil.py +++ b/extras/vpp_config/vpplib/VppGrubUtil.py @@ -101,12 +101,13 @@ class VppGrubUtil(object): value = cmdline.split('{}='.format(grubcmdline))[1] value = value.rstrip('"').lstrip('"') - iommu = re.findall(r'iommu=\w+', value) - pstate = re.findall(r'intel_pstate=\w+', value) + # jadfix intel_pstate=disable sometimes cause networks to hang on reboot + # iommu = re.findall(r'iommu=\w+', value) + # pstate = re.findall(r'intel_pstate=\w+', value) # If there is already some iommu commands set, leave them, # if not use ours - if iommu == [] and pstate == []: - value = '{} intel_pstate=disable'.format(value) + # if iommu == [] and pstate == []: + # value = '{} intel_pstate=disable'.format(value) # Replace isolcpus with ours isolcpus = re.findall(r'isolcpus=[\w+\-,]+', value) diff --git a/extras/vpp_config/vpplib/VppPCIUtil.py b/extras/vpp_config/vpplib/VppPCIUtil.py index 829d66ae374..591dfab96cd 100644 --- a/extras/vpp_config/vpplib/VppPCIUtil.py +++ b/extras/vpp_config/vpplib/VppPCIUtil.py @@ -251,11 +251,12 @@ class VppPCIUtil(object): :param devices: A list of devices :param show_interfaces: show the kernel information + :param show_header: Display the header if true :type devices: dict :type show_interfaces: bool + :type show_header: bool """ - if show_interfaces: header = "{:15} {:25} {:50}".format("PCI ID", "Kernel Interface(s)", @@ -265,8 +266,7 @@ class VppPCIUtil(object): "Description") dashseparator = ("-" * (len(header) - 2)) - - if show_header == True: + if show_header is True: print header print dashseparator for dit in devices.items(): @@ -298,7 +298,6 @@ class VppPCIUtil(object): :type device_id: string """ - rootdir = node['rootdir'] dpdk_script = rootdir + DPDK_SCRIPT cmd = dpdk_script + ' -u ' + ' ' + device_id -- cgit 1.2.3-korg