blob: 8c052a0277368f9bef159ce96c8cd50e9762c9b8 [file] [log] [blame]
# 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.
"""Library that supports Auto Configuration."""
from __future__ import absolute_import, division, print_function
import logging
import os
import re
from ipaddress import ip_address
import yaml
from vpplib.VPPUtil import VPPUtil
from vpplib.VppPCIUtil import VppPCIUtil
from vpplib.VppHugePageUtil import VppHugePageUtil
from vpplib.CpuUtils import CpuUtils
from vpplib.VppGrubUtil import VppGrubUtil
from vpplib.QemuUtils import QemuUtils
# Python2/3 compatible
try:
input = raw_input # noqa
except NameError:
pass
__all__ = ["AutoConfig"]
# Constants
MIN_SYSTEM_CPUS = 2
MIN_TOTAL_HUGE_PAGES = 1024
MAX_PERCENT_FOR_HUGE_PAGES = 70
IPERFVM_XML = 'configs/iperf-vm.xml'
IPERFVM_IMAGE = 'images/xenial-mod.img'
IPERFVM_ISO = 'configs/cloud-config.iso'
class AutoConfig(object):
"""Auto Configuration Tools"""
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
self._metadata = {}
self._nodes = {}
self._vpp_devices_node = {}
self._hugepage_config = ""
self._clean = clean
self._loadconfig()
self._sockfilename = ""
def get_nodes(self):
"""
Returns the nodes dictionary.
:returns: The nodes
:rtype: dictionary
"""
return self._nodes
@staticmethod
def _autoconfig_backup_file(filename):
"""
Create a backup file.
:param filename: The file to backup
:type filename: str
"""
# Does a copy of the file exist, if not create one
ofile = filename + '.orig'
(ret, stdout, stderr) = VPPUtil.exec_command('ls {}'.format(ofile))
if ret != 0:
logging.debug(stderr)
if stdout.strip('\n') != ofile:
cmd = 'sudo cp {} {}'.format(filename, ofile)
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
logging.debug(stderr)
# noinspection PyBroadException
@staticmethod
def _ask_user_ipv4():
"""
Asks the user for a number within a range.
default is returned if return is entered.
:returns: IP address with cidr
:rtype: str
"""
while True:
answer = input("Please enter the IPv4 Address [n.n.n.n/n]: ")
try:
ipinput = answer.split('/')
ipaddr = ip_address(ipinput[0])
if len(ipinput) > 1:
plen = answer.split('/')[1]
else:
answer = input("Please enter the netmask [n.n.n.n]: ")
plen = ip_address(answer).netmask_bits()
return '{}/{}'.format(ipaddr, plen)
except ValueError:
print("Please enter a valid IPv4 address.")
@staticmethod
def _ask_user_range(question, first, last, default):
"""
Asks the user for a number within a range.
default is returned if return is entered.
:param question: Text of a question.
:param first: First number in the range
:param last: Last number in the range
:param default: The value returned when return is entered
:type question: string
:type first: int
:type last: int
:type default: int
:returns: The answer to the question
:rtype: int
"""
while True:
answer = input(question)
if answer == '':
answer = default
break
if re.findall(r'[0-9+]', answer):
if int(answer) in range(first, last + 1):
break
else:
print("Please a value between {} and {} or Return.".
format(first, last))
else:
print("Please a number between {} and {} or Return.".
format(first, last))
return int(answer)
@staticmethod
def _ask_user_yn(question, default):
"""
Asks the user for a yes or no question.
:param question: Text of a question.
:param default: The value returned when return is entered
:type question: string
:type default: string
:returns: The answer to the question
:rtype: string
"""
input_valid = False
default = default.lower()
answer = ''
while not input_valid:
answer = input(question)
if answer == '':
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 _loadconfig(self):
"""
Load the testbed configuration, given the auto configuration file.
"""
# Get the Topology, from the topology layout file
topo = {}
with open(self._autoconfig_filename, 'r') as stream:
try:
topo = yaml.load(stream)
if 'metadata' in topo:
self._metadata = topo['metadata']
except yaml.YAMLError as exc:
raise RuntimeError(
"Couldn't read the Auto config file {}.".format(
self._autoconfig_filename, exc))
systemfile = self._rootdir + self._metadata['system_config_file']
if self._clean is False and os.path.isfile(systemfile):
with open(systemfile, 'r') as sysstream:
try:
systopo = yaml.load(sysstream)
if 'nodes' in systopo:
self._nodes = systopo['nodes']
except yaml.YAMLError as sysexc:
raise RuntimeError(
"Couldn't read the System config file {}.".format(
systemfile, sysexc))
else:
# Get the nodes from Auto Config
if 'nodes' in topo:
self._nodes = topo['nodes']
# Set the root directory in all the nodes
for i in self._nodes.items():
node = i[1]
node['rootdir'] = self._rootdir
def updateconfig(self):
"""
Update the testbed configuration, given the auto configuration file.
We will write the system configuration file with the current node
information
"""
# Initialize the yaml data
ydata = {'metadata': self._metadata, 'nodes': self._nodes}
# Write the system config file
filename = self._rootdir + self._metadata['system_config_file']
with open(filename, 'w') as yamlfile:
yaml.dump(ydata, yamlfile)
def _update_auto_config(self):
"""
Write the auto configuration file with the new configuration data,
input from the user.
"""
# Initialize the yaml data
nodes = {}
with open(self._autoconfig_filename, 'r') as stream:
try:
ydata = yaml.load(stream)
if 'nodes' in ydata:
nodes = ydata['nodes']
except yaml.YAMLError as exc:
print(exc)
return
for i in nodes.items():
key = i[0]
node = i[1]
# Interfaces
node['interfaces'] = {}
for item in self._nodes[key]['interfaces'].items():
port = item[0]
interface = item[1]
node['interfaces'][port] = {}
addr = '{}'.format(interface['pci_address'])
node['interfaces'][port]['pci_address'] = addr
if 'mac_address' in interface:
node['interfaces'][port]['mac_address'] = \
interface['mac_address']
if 'total_other_cpus' in self._nodes[key]['cpu']:
node['cpu']['total_other_cpus'] = \
self._nodes[key]['cpu']['total_other_cpus']
if 'total_vpp_cpus' in self._nodes[key]['cpu']:
node['cpu']['total_vpp_cpus'] = \
self._nodes[key]['cpu']['total_vpp_cpus']
if 'reserve_vpp_main_core' in self._nodes[key]['cpu']:
node['cpu']['reserve_vpp_main_core'] = \
self._nodes[key]['cpu']['reserve_vpp_main_core']
# TCP
if 'active_open_sessions' in self._nodes[key]['tcp']:
node['tcp']['active_open_sessions'] = \
self._nodes[key]['tcp']['active_open_sessions']
if 'passive_open_sessions' in self._nodes[key]['tcp']:
node['tcp']['passive_open_sessions'] = \
self._nodes[key]['tcp']['passive_open_sessions']
# Huge pages
node['hugepages']['total'] = self._nodes[key]['hugepages']['total']
# Write the auto config config file
with open(self._autoconfig_filename, 'w') as yamlfile:
yaml.dump(ydata, yamlfile)
def apply_huge_pages(self):
"""
Apply the huge page config
"""
for i in self._nodes.items():
node = i[1]
hpg = VppHugePageUtil(node)
hpg.hugepages_dryrun_apply()
@staticmethod
def _apply_vpp_unix(node):
"""
Apply the VPP Unix config
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
unix = ' nodaemon\n'
if 'unix' not in node['vpp']:
return ''
unixv = node['vpp']['unix']
if 'interactive' in unixv:
interactive = unixv['interactive']
if interactive is True:
unix = ' interactive\n'
return unix.rstrip('\n')
@staticmethod
def _apply_vpp_cpu(node):
"""
Apply the VPP cpu config
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
# Get main core
cpu = '\n'
if 'vpp_main_core' in node['cpu']:
vpp_main_core = node['cpu']['vpp_main_core']
else:
vpp_main_core = 0
if vpp_main_core != 0:
cpu += ' main-core {}\n'.format(vpp_main_core)
# Get workers
vpp_workers = node['cpu']['vpp_workers']
vpp_worker_len = len(vpp_workers)
if vpp_worker_len > 0:
vpp_worker_str = ''
for i, worker in enumerate(vpp_workers):
if i > 0:
vpp_worker_str += ','
if worker[0] == worker[1]:
vpp_worker_str += "{}".format(worker[0])
else:
vpp_worker_str += "{}-{}".format(worker[0], worker[1])
cpu += ' corelist-workers {}\n'.format(vpp_worker_str)
return cpu
@staticmethod
def _apply_vpp_devices(node):
"""
Apply VPP PCI Device configuration to vpp startup.
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
devices = ''
ports_per_numa = node['cpu']['ports_per_numa']
total_mbufs = node['cpu']['total_mbufs']
for item in ports_per_numa.items():
value = item[1]
interfaces = value['interfaces']
# if 0 was specified for the number of vpp workers, use 1 queue
num_rx_queues = None
num_tx_queues = None
if 'rx_queues' in value:
num_rx_queues = value['rx_queues']
if 'tx_queues' in value:
num_tx_queues = value['tx_queues']
num_rx_desc = None
num_tx_desc = None
# Create the devices string
for interface in interfaces:
pci_address = interface['pci_address']
pci_address = pci_address.lstrip("'").rstrip("'")
devices += '\n'
devices += ' dev {} {{ \n'.format(pci_address)
if num_rx_queues:
devices += ' num-rx-queues {}\n'.format(num_rx_queues)
else:
devices += ' num-rx-queues {}\n'.format(1)
if num_tx_queues:
devices += ' num-tx-queues {}\n'.format(num_tx_queues)
if num_rx_desc:
devices += ' num-rx-desc {}\n'.format(num_rx_desc)
if num_tx_desc:
devices += ' num-tx-desc {}\n'.format(num_tx_desc)
devices += ' }'
# If the total mbufs is not 0 or less than the default, set num-bufs
logging.debug("Total mbufs: {}".format(total_mbufs))
if total_mbufs != 0 and total_mbufs > 16384:
devices += '\n num-mbufs {}'.format(total_mbufs)
return devices
@staticmethod
def _calc_vpp_workers(node, vpp_workers, numa_node, other_cpus_end,
total_vpp_workers,
reserve_vpp_main_core):
"""
Calculate the VPP worker information
:param node: Node dictionary
:param vpp_workers: List of VPP workers
:param numa_node: Numa node
:param other_cpus_end: The end of the cpus allocated for cores
other than vpp
:param total_vpp_workers: The number of vpp workers needed
:param reserve_vpp_main_core: Is there a core needed for
the vpp main core
:type node: dict
:type numa_node: int
:type other_cpus_end: int
:type total_vpp_workers: int
:type reserve_vpp_main_core: bool
:returns: Is a core still needed for the vpp main core
:rtype: bool
"""
# Can we fit the workers in one of these slices
cpus = node['cpu']['cpus_per_node'][numa_node]
for cpu in cpus:
start = cpu[0]
end = cpu[1]
if start <= other_cpus_end:
start = other_cpus_end + 1
if reserve_vpp_main_core:
start += 1
workers_end = start + total_vpp_workers - 1
if workers_end <= end:
if reserve_vpp_main_core:
node['cpu']['vpp_main_core'] = start - 1
reserve_vpp_main_core = False
if total_vpp_workers:
vpp_workers.append((start, workers_end))
break
# We still need to reserve the main core
if reserve_vpp_main_core:
node['cpu']['vpp_main_core'] = other_cpus_end + 1
return reserve_vpp_main_core
@staticmethod
def _calc_desc_and_queues(total_numa_nodes,
total_ports_per_numa,
total_rx_queues,
ports_per_numa_value):
"""
Calculate the number of descriptors and queues
:param total_numa_nodes: The total number of numa nodes
:param total_ports_per_numa: The total number of ports for this
numa node
:param total_rx_queues: The total number of rx queues / port
:param ports_per_numa_value: The value from the ports_per_numa
dictionary
:type total_numa_nodes: int
:type total_ports_per_numa: int
:type total_rx_queues: int
:type ports_per_numa_value: dict
:returns The total number of message buffers
:rtype: int
"""
# Get the number of rx queues
rx_queues = max(1, total_rx_queues)
tx_queues = rx_queues * total_numa_nodes + 1
# Get the descriptor entries
desc_entries = 1024
ports_per_numa_value['rx_queues'] = rx_queues
total_mbufs = (((rx_queues * desc_entries) +
(tx_queues * desc_entries)) *
total_ports_per_numa)
total_mbufs = total_mbufs
return total_mbufs
@staticmethod
def _create_ports_per_numa(node, interfaces):
"""
Create a dictionary or ports per numa node
:param node: Node dictionary
:param interfaces: All the interfaces to be used by vpp
:type node: dict
:type interfaces: dict
:returns: The ports per numa dictionary
:rtype: dict
"""
# Make a list of ports by numa node
ports_per_numa = {}
for item in interfaces.items():
i = item[1]
if i['numa_node'] not in ports_per_numa:
ports_per_numa[i['numa_node']] = {'interfaces': []}
ports_per_numa[i['numa_node']]['interfaces'].append(i)
else:
ports_per_numa[i['numa_node']]['interfaces'].append(i)
node['cpu']['ports_per_numa'] = ports_per_numa
return ports_per_numa
def calculate_cpu_parameters(self):
"""
Calculate the cpu configuration.
"""
# Calculate the cpu parameters, needed for the
# vpp_startup and grub configuration
for i in self._nodes.items():
node = i[1]
# get total number of nic ports
interfaces = node['interfaces']
# Make a list of ports by numa node
ports_per_numa = self._create_ports_per_numa(node, interfaces)
# Get the number of cpus to skip, we never use the first cpu
other_cpus_start = 1
other_cpus_end = other_cpus_start + \
node['cpu']['total_other_cpus'] - 1
other_workers = None
if other_cpus_end != 0:
other_workers = (other_cpus_start, other_cpus_end)
node['cpu']['other_workers'] = other_workers
# Allocate the VPP main core and workers
vpp_workers = []
reserve_vpp_main_core = node['cpu']['reserve_vpp_main_core']
total_vpp_cpus = node['cpu']['total_vpp_cpus']
total_rx_queues = node['cpu']['total_rx_queues']
# If total_vpp_cpus is 0 or is less than the numa nodes with ports
# then we shouldn't get workers
total_workers_node = 0
if len(ports_per_numa):
total_workers_node = total_vpp_cpus // len(ports_per_numa)
total_main = 0
if reserve_vpp_main_core:
total_main = 1
total_mbufs = 0
if total_main + total_workers_node != 0:
for item in ports_per_numa.items():
numa_node = item[0]
value = item[1]
# Get the number of descriptors and queues
mbufs = self._calc_desc_and_queues(
len(ports_per_numa),
len(value['interfaces']), total_rx_queues, value)
total_mbufs += mbufs
# Get the VPP workers
reserve_vpp_main_core = self._calc_vpp_workers(
node, vpp_workers, numa_node,
other_cpus_end, total_workers_node,
reserve_vpp_main_core)
total_mbufs *= 2.5
total_mbufs = int(total_mbufs)
else:
total_mbufs = 0
# Save the info
node['cpu']['vpp_workers'] = vpp_workers
node['cpu']['total_mbufs'] = total_mbufs
# Write the config
self.updateconfig()
@staticmethod
def _apply_vpp_tcp(node):
"""
Apply the VPP Unix config
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
active_open_sessions = node['tcp']['active_open_sessions']
aos = int(active_open_sessions)
passive_open_sessions = node['tcp']['passive_open_sessions']
pos = int(passive_open_sessions)
# Generate the api-segment gid vpp sheit in any case
if (aos + pos) == 0:
tcp = '\n'.join([
"api-segment {",
" gid vpp",
"}"
])
return tcp.rstrip('\n')
tcp = '\n'.join([
"# TCP stack-related configuration parameters",
"# expecting {:d} client sessions, {:d} server sessions\n".format(
aos, pos),
"heapsize 4g\n",
"api-segment {",
" global-size 2000M",
" api-size 1G",
"}\n",
"session {",
" event-queue-length {:d}".format(aos + pos),
" preallocated-sessions {:d}".format(aos + pos),
" v4-session-table-buckets {:d}".format((aos + pos) // 4),
" v4-session-table-memory 3g\n"
])
if aos > 0:
tcp = tcp + " v4-halfopen-table-buckets {:d}".format(
(aos + pos) // 4) + "\n"
tcp = tcp + " v4-halfopen-table-memory 3g\n"
tcp = tcp + " local-endpoints-table-buckets {:d}".format(
(aos + pos) // 4) + "\n"
tcp = tcp + " local-endpoints-table-memory 3g\n"
tcp = tcp + "}\n\n"
tcp = tcp + "tcp {\n"
tcp = tcp + " preallocated-connections {:d}".format(aos + pos) + "\n"
if aos > 0:
tcp = tcp + " preallocated-half-open-connections {:d}".format(
aos) + "\n"
tcp = tcp + "}\n\n"
return tcp.rstrip('\n')
def apply_vpp_startup(self):
"""
Apply the vpp startup configration
"""
# Apply the VPP startup configruation
for i in self._nodes.items():
node = i[1]
# Get the startup file
rootdir = node['rootdir']
sfile = rootdir + node['vpp']['startup_config_file']
# Get the devices
devices = self._apply_vpp_devices(node)
# Get the CPU config
cpu = self._apply_vpp_cpu(node)
# Get the unix config
unix = self._apply_vpp_unix(node)
# Get the TCP configuration, if any
tcp = self._apply_vpp_tcp(node)
# Make a backup if needed
self._autoconfig_backup_file(sfile)
# Get the template
tfile = sfile + '.template'
(ret, stdout, stderr) = \
VPPUtil.exec_command('cat {}'.format(tfile))
if ret != 0:
raise RuntimeError('Executing cat command failed to node {}'.
format(node['host']))
startup = stdout.format(unix=unix,
cpu=cpu,
devices=devices,
tcp=tcp)
(ret, stdout, stderr) = \
VPPUtil.exec_command('rm {}'.format(sfile))
if ret != 0:
logging.debug(stderr)
cmd = "sudo cat > {0} << EOF\n{1}\n".format(sfile, startup)
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
raise RuntimeError('Writing config failed node {}'.
format(node['host']))
def apply_grub_cmdline(self):
"""
Apply the grub cmdline
"""
for i in self._nodes.items():
node = i[1]
# Get the isolated CPUs
other_workers = node['cpu']['other_workers']
vpp_workers = node['cpu']['vpp_workers']
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]
if vpp_main_core != 0:
all_workers += [(vpp_main_core, vpp_main_core)]
all_workers += vpp_workers
isolated_cpus = ''
for idx, worker in enumerate(all_workers):
if worker is None:
continue
if idx > 0:
isolated_cpus += ','
if worker[0] == worker[1]:
isolated_cpus += "{}".format(worker[0])
else:
isolated_cpus += "{}-{}".format(worker[0], worker[1])
vppgrb = VppGrubUtil(node)
current_cmdline = vppgrb.get_current_cmdline()
if 'grub' not in node:
node['grub'] = {}
node['grub']['current_cmdline'] = current_cmdline
node['grub']['default_cmdline'] = \
vppgrb.apply_cmdline(node, isolated_cpus)
self.updateconfig()
def get_hugepages(self):
"""
Get the hugepage configuration
"""
for i in self._nodes.items():
node = i[1]
hpg = VppHugePageUtil(node)
max_map_count, shmmax = hpg.get_huge_page_config()
node['hugepages']['max_map_count'] = max_map_count
node['hugepages']['shmax'] = shmmax
total, free, size, memtotal, memfree = hpg.get_actual_huge_pages()
node['hugepages']['actual_total'] = total
node['hugepages']['free'] = free
node['hugepages']['size'] = size
node['hugepages']['memtotal'] = memtotal
node['hugepages']['memfree'] = memfree
self.updateconfig()
def get_grub(self):
"""
Get the grub configuration
"""
for i in self._nodes.items():
node = i[1]
vppgrb = VppGrubUtil(node)
current_cmdline = vppgrb.get_current_cmdline()
default_cmdline = vppgrb.get_default_cmdline()
# Get the total number of isolated CPUs
current_iso_cpus = 0
iso_cpur = re.findall(r'isolcpus=[\w+\-,]+', current_cmdline)
iso_cpurl = len(iso_cpur)
if iso_cpurl > 0:
iso_cpu_str = iso_cpur[0]
iso_cpu_str = iso_cpu_str.split('=')[1]
iso_cpul = iso_cpu_str.split(',')
for iso_cpu in iso_cpul:
isocpuspl = iso_cpu.split('-')
if len(isocpuspl) == 1:
current_iso_cpus += 1
else:
first = int(isocpuspl[0])
second = int(isocpuspl[1])
if first == second:
current_iso_cpus += 1
else:
current_iso_cpus += second - first
if 'grub' not in node:
node['grub'] = {}
node['grub']['current_cmdline'] = current_cmdline
node['grub']['default_cmdline'] = default_cmdline
node['grub']['current_iso_cpus'] = current_iso_cpus
self.updateconfig()
@staticmethod
def _get_device(node):
"""
Get the device configuration for a single node
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
vpp = VppPCIUtil(node)
vpp.get_all_devices()
# Save the device information
node['devices'] = {}
node['devices']['dpdk_devices'] = vpp.get_dpdk_devices()
node['devices']['kernel_devices'] = vpp.get_kernel_devices()
node['devices']['other_devices'] = vpp.get_other_devices()
node['devices']['linkup_devices'] = vpp.get_link_up_devices()
def get_devices_per_node(self):
"""
Get the device configuration for all the nodes
"""
for i in self._nodes.items():
node = i[1]
# Update the interface data
self._get_device(node)
self.updateconfig()
@staticmethod
def get_cpu_layout(node):
"""
Get the cpu layout
using lscpu -p get the cpu layout.
Returns a list with each item representing a single cpu.
:param node: Node dictionary.
:type node: dict
:returns: The cpu layout
:rtype: list
"""
cmd = 'lscpu -p'
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
raise RuntimeError('{} failed on node {} {}'.
format(cmd, node['host'], stderr))
pcpus = []
lines = stdout.split('\n')
for line in lines:
if line == '' or line[0] == '#':
continue
linesplit = line.split(',')
layout = {'cpu': linesplit[0], 'core': linesplit[1],
'socket': linesplit[2], 'node': linesplit[3]}
# cpu, core, socket, node
pcpus.append(layout)
return pcpus
def get_cpu(self):
"""
Get the cpu configuration
"""
# Get the CPU layout
CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)
for i in self._nodes.items():
node = i[1]
# Get the cpu layout
layout = self.get_cpu_layout(node)
node['cpu']['layout'] = layout
cpuinfo = node['cpuinfo']
smt_enabled = CpuUtils.is_smt_enabled(cpuinfo)
node['cpu']['smt_enabled'] = smt_enabled
# We don't want to write the cpuinfo
node['cpuinfo'] = ""
# Write the config
self.updateconfig()
def discover(self):
"""
Get the current system configuration.
"""
# Get the Huge Page configuration
self.get_hugepages()
# Get the device configuration
self.get_devices_per_node()
# Get the CPU configuration
self.get_cpu()
# Get the current grub cmdline
self.get_grub()
def _modify_cpu_questions(self, node, total_cpus, numa_nodes):
"""
Ask the user questions related to the cpu configuration.
:param node: Node dictionary
:param total_cpus: The total number of cpus in the system
:param numa_nodes: The list of numa nodes in the system
:type node: dict
:type total_cpus: int
:type numa_nodes: list
"""
print("\nYour system has {} core(s) and {} Numa Nodes.".
format(total_cpus, len(numa_nodes)))
print("To begin, we suggest not reserving any cores for "
"VPP or other processes.")
print("Then to improve performance start reserving cores and "
"adding queues as needed.")
# Leave 1 for the general system
total_cpus -= 1
max_vpp_cpus = min(total_cpus, 4)
total_vpp_cpus = 0
if max_vpp_cpus > 0:
question = "\nHow many core(s) shall we reserve for " \
"VPP [0-{}][0]? ".format(max_vpp_cpus)
total_vpp_cpus = self._ask_user_range(question, 0, max_vpp_cpus, 0)
node['cpu']['total_vpp_cpus'] = total_vpp_cpus
total_other_cpus = 0
max_other_cores = total_cpus - total_vpp_cpus
if max_other_cores > 0:
question = 'How many core(s) do you want to reserve for ' \
'processes other than VPP? [0-{}][0]? '. \
format(str(max_other_cores))
total_other_cpus = self._ask_user_range(question, 0, max_other_cores, 0)
node['cpu']['total_other_cpus'] = total_other_cpus
max_main_cpus = total_cpus - total_vpp_cpus - total_other_cpus
reserve_vpp_main_core = False
if max_main_cpus > 0:
question = "Should we reserve 1 core for the VPP Main thread? "
question += "[y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
reserve_vpp_main_core = True
node['cpu']['reserve_vpp_main_core'] = reserve_vpp_main_core
node['cpu']['vpp_main_core'] = 0
question = "How many RX queues per port shall we use for " \
"VPP [1-4][1]? ".format(max_vpp_cpus)
total_rx_queues = self._ask_user_range(question, 1, 4, 1)
node['cpu']['total_rx_queues'] = total_rx_queues
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
CpuUtils.get_cpu_layout_from_all_nodes(self._nodes)
for i in self._nodes.items():
node = i[1]
total_cpus = 0
total_cpus_per_slice = 0
cpus_per_node = {}
numa_nodes = []
cores = []
cpu_layout = self.get_cpu_layout(node)
# Assume the number of cpus per slice is always the same as the
# first slice
first_node = '0'
for cpu in cpu_layout:
if cpu['node'] != first_node:
break
total_cpus_per_slice += 1
# Get the total number of cpus, cores, and numa nodes from the
# cpu layout
for cpul in cpu_layout:
numa_node = cpul['node']
core = cpul['core']
cpu = cpul['cpu']
total_cpus += 1
if numa_node not in cpus_per_node:
cpus_per_node[numa_node] = []
cpuperslice = int(cpu) % total_cpus_per_slice
if cpuperslice == 0:
cpus_per_node[numa_node].append((int(cpu), int(cpu) +
total_cpus_per_slice - 1))
if numa_node not in numa_nodes:
numa_nodes.append(numa_node)
if core not in cores:
cores.append(core)
node['cpu']['cpus_per_node'] = cpus_per_node
# Ask the user some questions
if ask_questions and total_cpus >= 4:
self._modify_cpu_questions(node, total_cpus, numa_nodes)
# Populate the interfaces with the numa node
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'] = ""
# Write the configs
self._update_auto_config()
self.updateconfig()
def _modify_other_devices(self, node,
other_devices, kernel_devices, dpdk_devices):
"""
Modify the devices configuration, asking for the user for the values.
"""
odevices_len = len(other_devices)
if odevices_len > 0:
print("\nThese device(s) are currently NOT being used "
"by VPP or the OS.\n")
VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
question = "\nWould you like to give any of these devices"
question += " back to the OS [Y/n]? "
answer = self._ask_user_yn(question, 'Y')
if answer == 'y':
vppd = {}
for dit in other_devices.items():
dvid = dit[0]
device = dit[1]
question = "Would you like to use device {} for". \
format(dvid)
question += " the OS [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
if 'unused' in device and len(
device['unused']) != 0 and \
device['unused'][0] != '':
driver = device['unused'][0]
ret = VppPCIUtil.bind_vpp_device(
node, driver, dvid)
if ret:
logging.debug(
'Could not bind device {}'.format(dvid))
else:
vppd[dvid] = device
for dit in vppd.items():
dvid = dit[0]
device = dit[1]
kernel_devices[dvid] = device
del other_devices[dvid]
odevices_len = len(other_devices)
if odevices_len > 0:
print("\nThese device(s) are still NOT being used "
"by VPP or the OS.\n")
VppPCIUtil.show_vpp_devices(other_devices, show_interfaces=False)
question = "\nWould you like use any of these for VPP [y/N]? "
answer = self._ask_user_yn(question, 'N')
if answer == 'y':
vppd = {}
for dit in other_devices.items():
dvid = dit[0]
device = dit[1]
question = "Would you like to use device {} ".format(dvid)
question += "for VPP [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
vppd[dvid] = device
for dit in vppd.items():
dvid = dit[0]
device = dit[1]
if 'unused' in device and len(device['unused']) != 0 and \
device['unused'][0] != '':
driver = device['unused'][0]
logging.debug(
'Binding device {} to driver {}'.format(dvid,
driver))
ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
if ret:
logging.debug(
'Could not bind device {}'.format(dvid))
else:
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.
"""
for i in self._nodes.items():
node = i[1]
devices = node['devices']
other_devices = devices['other_devices']
kernel_devices = devices['kernel_devices']
dpdk_devices = devices['dpdk_devices']
if other_devices:
self._modify_other_devices(node, other_devices,
kernel_devices, dpdk_devices)
# Get the devices again for this node
self._get_device(node)
devices = node['devices']
kernel_devices = devices['kernel_devices']
dpdk_devices = devices['dpdk_devices']
klen = len(kernel_devices)
if klen > 0:
print("\nThese devices are safe to be used with VPP.\n")
VppPCIUtil.show_vpp_devices(kernel_devices)
question = "\nWould you like to use any of these " \
"device(s) for VPP [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
vppd = {}
for dit in kernel_devices.items():
dvid = dit[0]
device = dit[1]
question = "Would you like to use device {} ".format(dvid)
question += "for VPP [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
vppd[dvid] = device
for dit in vppd.items():
dvid = dit[0]
device = dit[1]
if 'unused' in device and len(
device['unused']) != 0 and device['unused'][
0] != '':
driver = device['unused'][0]
question = "Would you like to bind the driver {} for {} [y/N]? ".format(driver, dvid)
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
logging.debug('Binding device {} to driver {}'.format(dvid, driver))
ret = VppPCIUtil.bind_vpp_device(node, driver, dvid)
if ret:
logging.debug('Could not bind device {}'.format(dvid))
dpdk_devices[dvid] = device
del kernel_devices[dvid]
dlen = len(dpdk_devices)
if dlen > 0:
print("\nThese device(s) are already using DPDK.\n")
VppPCIUtil.show_vpp_devices(dpdk_devices,
show_interfaces=False)
question = "\nWould you like to remove any of "
question += "these device(s) [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
vppdl = {}
for dit in dpdk_devices.items():
dvid = dit[0]
device = dit[1]
question = "Would you like to remove {} [y/N]? ". \
format(dvid)
answer = self._ask_user_yn(question, 'n')
if answer == 'y':
vppdl[dvid] = device
for dit in vppdl.items():
dvid = dit[0]
device = dit[1]
if 'unused' in device and len(
device['unused']) != 0 and device['unused'][
0] != '':
driver = device['unused'][0]
logging.debug(
'Binding device {} to driver {}'.format(
dvid, driver))
ret = VppPCIUtil.bind_vpp_device(node, driver,
dvid)
if ret:
logging.debug(
'Could not bind device {}'.format(dvid))
else:
kernel_devices[dvid] = device
del dpdk_devices[dvid]
interfaces = {}
for dit in dpdk_devices.items():
dvid = dit[0]
device = dit[1]
VppPCIUtil.vpp_create_interface(interfaces, dvid, device)
node['interfaces'] = interfaces
self._update_auto_config()
self.updateconfig()
def modify_huge_pages(self):
"""
Modify the huge page configuration, asking for the user for the values.
"""
for i in self._nodes.items():
node = i[1]
total = node['hugepages']['actual_total']
free = node['hugepages']['free']
size = node['hugepages']['size']
memfree = node['hugepages']['memfree'].split(' ')[0]
hugesize = int(size.split(' ')[0])
# The max number of huge pages should be no more than
# 70% of total free memory
maxpages = (int(memfree) * MAX_PERCENT_FOR_HUGE_PAGES // 100) // \
hugesize
print("\nThere currently {} {} huge pages free.".format(
free, size))
question = "Do you want to reconfigure the number of " \
"huge pages [y/N]? "
answer = self._ask_user_yn(question, 'n')
if answer == 'n':
node['hugepages']['total'] = total
continue
print("\nThere currently a total of {} huge pages.".
format(total))
question = "How many huge pages do you want [{} - {}][{}]? ". \
format(MIN_TOTAL_HUGE_PAGES, maxpages, MIN_TOTAL_HUGE_PAGES)
answer = self._ask_user_range(question, 1024, maxpages, 1024)
node['hugepages']['total'] = str(answer)
# Update auto-config.yaml
self._update_auto_config()
# Rediscover just the hugepages
self.get_hugepages()
def get_tcp_params(self):
"""
Get the tcp configuration
"""
# maybe nothing to do here?
self.updateconfig()
def acquire_tcp_params(self):
"""
Ask the user for TCP stack configuration parameters
"""
for i in self._nodes.items():
node = i[1]
question = "\nHow many active-open / tcp client sessions are " \
"expected [0-10000000][0]? "
answer = self._ask_user_range(question, 0, 10000000, 0)
# Less than 10K is equivalent to 0
if int(answer) < 10000:
answer = 0
node['tcp']['active_open_sessions'] = answer
question = "How many passive-open / tcp server sessions are " \
"expected [0-10000000][0]? "
answer = self._ask_user_range(question, 0, 10000000, 0)
# Less than 10K is equivalent to 0
if int(answer) < 10000:
answer = 0
node['tcp']['passive_open_sessions'] = answer
# Update auto-config.yaml
self._update_auto_config()
# Rediscover tcp parameters
self.get_tcp_params()
@staticmethod
def patch_qemu(node):
"""
Patch qemu with the correct patches.
:param node: Node dictionary
:type node: dict
"""
print('\nWe are patching the node "{}":\n'.format(node['host']))
QemuUtils.build_qemu(node, force_install=True, apply_patch=True)
@staticmethod
def cpu_info(node):
"""
print the CPU information
"""
cpu = CpuUtils.get_cpu_info_per_node(node)
item = 'Model name'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'CPU(s)'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'Thread(s) per core'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'Core(s) per socket'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'Socket(s)'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'NUMA node(s)'
numa_nodes = 0
if item in cpu:
numa_nodes = int(cpu[item])
for i in range(0, numa_nodes):
item = "NUMA node{} CPU(s)".format(i)
print("{:>20}: {}".format(item, cpu[item]))
item = 'CPU max MHz'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
item = 'CPU min MHz'
if item in cpu:
print("{:>20}: {}".format(item, cpu[item]))
if node['cpu']['smt_enabled']:
smt = 'Enabled'
else:
smt = 'Disabled'
print("{:>20}: {}".format('SMT', smt))
# VPP Threads
print("\nVPP Threads: (Name: Cpu Number)")
vpp_processes = cpu['vpp_processes']
for i in vpp_processes.items():
print(" {:10}: {:4}".format(i[0], i[1]))
@staticmethod
def device_info(node):
"""
Show the device information.
"""
if 'cpu' in node and 'total_mbufs' in node['cpu']:
total_mbufs = node['cpu']['total_mbufs']
if total_mbufs != 0:
print("Total Number of Buffers: {}".format(total_mbufs))
vpp = VppPCIUtil(node)
vpp.get_all_devices()
linkup_devs = vpp.get_link_up_devices()
if len(linkup_devs):
print("\nDevices with link up (can not be used with VPP):")
vpp.show_vpp_devices(linkup_devs, show_header=False)
# for dev in linkup_devs:
# print (" " + dev)
kernel_devs = vpp.get_kernel_devices()
if len(kernel_devs):
print("\nDevices bound to kernel drivers:")
vpp.show_vpp_devices(kernel_devs, show_header=False)
else:
print("\nNo devices bound to kernel drivers")
dpdk_devs = vpp.get_dpdk_devices()
if len(dpdk_devs):
print("\nDevices bound to DPDK drivers:")
vpp.show_vpp_devices(dpdk_devs, show_interfaces=True,
show_header=False)
else:
print("\nNo devices bound to DPDK drivers")
other_devs = vpp.get_other_devices()
if len(other_devs):
print("\nDevices not bound to Kernel or DPDK drivers:")
vpp.show_vpp_devices(other_devs, show_interfaces=True,
show_header=False)
else:
print("\nNo devices not bound to Kernel or DPDK drivers")
vpputl = VPPUtil()
interfaces = vpputl.get_hardware(node)
if interfaces == {}:
return
print("\nDevices in use by VPP:")
if len(interfaces.items()) < 2:
print("None")
return
print("{:30} {:4} {:4} {:7} {:4} {:7}".
format('Name', 'Numa', 'RXQs',
'RXDescs', 'TXQs', 'TXDescs'))
for intf in sorted(interfaces.items()):
name = intf[0]
value = intf[1]
if name == 'local0':
continue
numa = rx_qs = rx_ds = tx_qs = tx_ds = ''
if 'numa' in value:
numa = int(value['numa'])
if 'rx queues' in value:
rx_qs = int(value['rx queues'])
if 'rx descs' in value:
rx_ds = int(value['rx descs'])
if 'tx queues' in value:
tx_qs = int(value['tx queues'])
if 'tx descs' in value:
tx_ds = int(value['tx descs'])
print("{:30} {:>4} {:>4} {:>7} {:>4} {:>7}".
format(name, numa, rx_qs, rx_ds, tx_qs, tx_ds))
@staticmethod
def hugepage_info(node):
"""
Show the huge page information.
"""
hpg = VppHugePageUtil(node)
hpg.show_huge_pages()
@staticmethod
def has_interfaces(node):
"""
Check for interfaces, return tru if there is at least one
:returns: boolean
"""
if 'interfaces' in node and len(node['interfaces']):
return True
else:
return False
@staticmethod
def min_system_resources(node):
"""
Check the system for basic minimum resources, return true if
there is enough.
:returns: boolean
"""
min_sys_res = True
# CPUs
if 'layout' in node['cpu']:
total_cpus = len(node['cpu']['layout'])
if total_cpus < 2:
print("\nThere is only {} CPU(s) available on this system. "
"This is not enough to run VPP.".format(total_cpus))
min_sys_res = False
# System Memory
if 'free' in node['hugepages'] and \
'memfree' in node['hugepages'] and \
'size' in node['hugepages']:
free = node['hugepages']['free']
memfree = float(node['hugepages']['memfree'].split(' ')[0])
hugesize = float(node['hugepages']['size'].split(' ')[0])
memhugepages = MIN_TOTAL_HUGE_PAGES * hugesize
percentmemhugepages = (memhugepages / memfree) * 100
if free is '0' and \
percentmemhugepages > MAX_PERCENT_FOR_HUGE_PAGES:
print(
"\nThe System has only {} of free memory. You will not "
"be able to allocate enough Huge Pages for VPP.".format(
int(
memfree))
)
min_sys_res = False
return min_sys_res
def sys_info(self):
"""
Print the system information
"""
for i in self._nodes.items():
print("\n==============================")
name = i[0]
node = i[1]
print("NODE: {}\n".format(name))
# CPU
print("CPU:")
self.cpu_info(node)
# Grub
print("\nGrub Command Line:")
if 'grub' in node:
print(" Current: {}".format(
node['grub']['current_cmdline']))
print(" Configured: {}".format(
node['grub']['default_cmdline']))
# Huge Pages
print("\nHuge Pages:")
self.hugepage_info(node)
# Devices
print("\nDevices:")
self.device_info(node)
# Status
print("\nVPP Service Status:")
state, errors = VPPUtil.status(node)
print(" {}".format(state))
for e in errors:
print(" {}".format(e))
# Minimum system resources
self.min_system_resources(node)
print("\n==============================")
def _ipv4_interface_setup_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: dict
"""
vpputl = VPPUtil()
interfaces = vpputl.get_hardware(node)
if interfaces == {}:
return
interfaces_with_ip = []
for intf in sorted(interfaces.items()):
name = intf[0]
if name == 'local0':
continue
question = "Would you like add address to " \
"interface {} [Y/n]? ".format(name)
answer = self._ask_user_yn(question, 'y')
if answer == 'y':
address = {}
addr = self._ask_user_ipv4()
address['name'] = name
address['addr'] = addr
interfaces_with_ip.append(address)
return interfaces_with_ip
def ipv4_interface_setup(self):
"""
After asking the user some questions, get a list of interfaces
and IPv4 addresses associated with those interfaces
"""
for i in self._nodes.items():
node = i[1]
# Show the current interfaces with IP addresses
current_ints = VPPUtil.get_int_ip(node)
if current_ints != {}:
print("\nThese are the current interfaces with IP addresses:")
for items in sorted(current_ints.items()):
name = items[0]
value = items[1]
if 'address' not in value:
address = 'Not Set'
else:
address = value['address']
print("{:30} {:20} {:10}".format(name, address,
value['state']))
question = "\nWould you like to keep this configuration " \
"[Y/n]? "
answer = self._ask_user_yn(question, 'y')
if answer == 'y':
continue
else:
print("\nThere are currently no interfaces with IP "
"addresses.")
# Create a script that add the ip addresses to the interfaces
# and brings the interfaces up
ints_with_addrs = self._ipv4_interface_setup_questions(node)
content = ''
for ints in ints_with_addrs:
name = ints['name']
addr = ints['addr']
setipstr = 'set int ip address {} {}\n'.format(name, addr)
setintupstr = 'set int state {} up\n'.format(name)
content += setipstr + setintupstr
# Write the content to the script
rootdir = node['rootdir']
filename = rootdir + '/vpp/vpp-config/scripts/set_int_ipv4_and_up'
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))
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 = '/var/run/vpp/{}.sock'.format(
name.replace('/', '_'))
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(
"Couldn't execute the command {}, {}.".format(cmd,
stderr))
vintname = stdout.rstrip('\r\n')
cmd = 'chmod 777 {}'.format(sockfilename)
(ret, stdout, stderr) = vpputl.exec_command(cmd)
if ret != 0:
raise RuntimeError(
"Couldn't execute the command {}, {}.".format(cmd,
stderr))
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 = '\n'.join([
'comment { The following command creates the socket }',
'comment { and returns a virtual interface }',
'comment {{ create vhost-user socket '
'/var/run/vpp/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))
def _iperf_vm_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
while True:
print('\nPlease pick one interface to connect to the iperf VM.')
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, 'n')
if answer == 'y':
self._sockfilename = '/var/run/vpp/{}.sock'.format(
name.replace('/', '_'))
if os.path.exists(self._sockfilename):
os.remove(self._sockfilename)
cmd = 'vppctl create vhost-user socket {} server'.format(
self._sockfilename)
(ret, stdout, stderr) = vpputl.exec_command(cmd)
if ret != 0:
raise RuntimeError(
"Couldn't execute the command {}, {}.".format(
cmd, stderr))
vintname = stdout.rstrip('\r\n')
cmd = 'chmod 777 {}'.format(self._sockfilename)
(ret, stdout, stderr) = vpputl.exec_command(cmd)
if ret != 0:
raise RuntimeError(
"Couldn't execute the command {}, {}.".format(
cmd, stderr))
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_iperf_virtual_interface(self):
"""
After asking the user some questions, and create and bridge a
virtual interface to be used with iperf VM
"""
for i in self._nodes.items():
node = i[1]
# Show the current bridge and interface configuration
print("\nThis the current bridge configuration:")
ifaces = VPPUtil.show_bridge(node)
question = "\nWould you like to keep this configuration [Y/n]? "
answer = self._ask_user_yn(question, 'y')
if answer == 'y':
self._sockfilename = '/var/run/vpp/{}.sock'.format(
ifaces[0]['name'].replace('/', '_'))
if os.path.exists(self._sockfilename):
continue
# Create a script that builds a bridge configuration with
# physical interfaces and virtual interfaces
ints_with_vints = self._iperf_vm_questions(node)
content = ''
for intf in ints_with_vints:
vhoststr = '\n'.join([
'comment { The following command creates the socket }',
'comment { and returns a virtual interface }',
'comment {{ create vhost-user socket '
'/var/run/vpp/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_iperf_vm'
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))
@staticmethod
def destroy_iperf_vm(name):
"""
After asking the user some questions, create a VM and connect
the interfaces to VPP interfaces
:param name: The name of the VM to be be destroyed
:type name: str
"""
cmd = 'virsh list'
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
logging.debug(stderr)
raise RuntimeError(
"Couldn't execute the command {} : {}".format(cmd, stderr))
if re.findall(name, stdout):
cmd = 'virsh destroy {}'.format(name)
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
logging.debug(stderr)
raise RuntimeError(
"Couldn't execute the command {} : {}".format(
cmd, stderr))
def create_iperf_vm(self, vmname):
"""
After asking the user some questions, create a VM and connect
the interfaces to VPP interfaces
"""
# Read the iperf VM template file
distro = VPPUtil.get_linux_distro()
if distro[0] == 'Ubuntu':
tfilename = \
'{}/vpp/vpp-config/configs/iperf-ubuntu.xml.template'.format(
self._rootdir)
else:
tfilename = \
'{}/vpp/vpp-config/configs/iperf-centos.xml.template'.format(
self._rootdir)
with open(tfilename, 'r') as tfile:
tcontents = tfile.read()
tfile.close()
# Add the variables
imagename = '{}/vpp/vpp-config/{}'.format(
self._rootdir, IPERFVM_IMAGE)
isoname = '{}/vpp/vpp-config/{}'.format(self._rootdir, IPERFVM_ISO)
tcontents = tcontents.format(vmname=vmname, imagename=imagename,
isoname=isoname,
vhostsocketname=self._sockfilename)
# Write the xml
ifilename = '{}/vpp/vpp-config/{}'.format(self._rootdir, IPERFVM_XML)
with open(ifilename, 'w+') as ifile:
ifile.write(tcontents)
ifile.close()
cmd = 'virsh create {}'.format(ifilename)
(ret, stdout, stderr) = VPPUtil.exec_command(cmd)
if ret != 0:
logging.debug(stderr)
raise RuntimeError(
"Couldn't execute the command {} : {}".format(cmd, stderr))