blob: 9a79039f69e2fb6fbce39fe501cd8a92d9dc61e1 [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_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"]
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 += " }"
return devices
@staticmethod
def _apply_buffers(node):
"""
Apply VPP PCI Device configuration to vpp startup.
:param node: Node dictionary with cpuinfo.
:type node: dict
"""
buffers = ""
total_mbufs = node["cpu"]["total_mbufs"]
# 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:
buffers += " buffers-per-numa {}".format(total_mbufs)
return buffers
@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
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 tcp 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 buffers
devices = self._apply_vpp_devices(node)
# Get the CPU config
cpu = self._apply_vpp_cpu(node)
# Get the buffer configuration
buffers = self._apply_buffers(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(cpu=cpu, buffers=buffers, 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)
)