Page MenuHomeVyOS Platform

No OneTemporary

Size
63 KB
Referenced Files
None
Subscribers
None
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index 03ee68beb..86b00da44 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -1,1027 +1,1069 @@
#!/usr/bin/env python3
#
# Copyright (C) 2023-2025 VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import os
import re
import unittest
from json import loads
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import process_named_running
from vyos.utils.file import read_file
from vyos.utils.process import rc_cmd
PROCESS_NAME = 'vpp_main'
VPP_CONF = '/run/vpp/vpp.conf'
base_path = ['vpp']
driver = 'dpdk'
interface = 'eth1'
def get_address(interface):
rc, data = rc_cmd(f'ip --json address show dev {interface}')
if rc == 0:
data = loads(data)
if isinstance(data, list) and len(data) > 0:
ip_address = data[0]['addr_info'][0]['local']
return ip_address
class TestVPP(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
super(TestVPP, cls).setUpClass()
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
def setUp(self):
self.cli_set(base_path + ['settings', 'interface', interface, 'driver', driver])
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '10'])
def tearDown(self):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
# delete test config
self.cli_delete(base_path)
self.cli_commit()
self.assertFalse(os.path.exists(VPP_CONF))
self.assertFalse(process_named_running(PROCESS_NAME))
def test_01_vpp_basic(self):
main_core = '0'
poll_sleep = '0'
# Main core must be verified
# expect raise ConfigError
self.cli_set(base_path + ['settings', 'cpu', 'main-core', '99'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', poll_sleep])
# commit changes
self.cli_commit()
config_entries = (
f'poll-sleep-usec {poll_sleep}',
f'main-core {main_core}',
'plugin default { disable }',
'plugin dpdk_plugin.so { enable }',
'plugin linux_cp_plugin.so { enable }',
'dev 0000:00:00.0',
'uio-bind-force',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
# route-no-paths is not present in the output
# looks like vpp bug
_, out = rc_cmd('sudo vppctl show lcp')
required_str = 'lcp route-no-paths on'
self.assertIn(required_str, out)
self.cli_set(base_path + ['settings', 'lcp', 'ignore-kernel-routes'])
self.cli_commit()
# check disabled 'route no path'
_, out = rc_cmd('sudo vppctl show lcp')
required_str = 'lcp route-no-paths off'
self.assertIn(required_str, out)
def test_02_vpp_vxlan(self):
vni = '23'
interface_vxlan = f'vxlan{vni}'
interface_kernel = f'vpptap{vni}'
new_interface_kernel = f'vpptap1{vni}'
source_address = '192.0.2.1'
new_source_address = '192.0.2.3'
remote_address = '192.0.2.254'
kernel_address = '203.0.113.1'
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/24']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check vxlan interface
_, out = rc_cmd('sudo vppctl show vxlan tunnel')
required_str = f'[0] instance 23 src {source_address} dst {remote_address} src_port 4789 dst_port 4789 vni {vni}'
self.assertIn(required_str, out)
# update vxlan interface
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'source-address',
new_source_address,
]
)
self.cli_commit()
# check gre interface after update
_, out = rc_cmd('sudo vppctl show vxlan tunnel')
required_str = (
f'[0] instance {vni} src {new_source_address} dst {remote_address}'
)
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# change vpp settings
self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '5'])
self.cli_commit()
config = read_file(VPP_CONF)
self.assertIn('poll-sleep-usec 5', config)
# delete vxlan kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update vxlan kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'vxlan',
interface_vxlan,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete vxlan kernel-interface
self.cli_delete(
base_path + ['interfaces', 'vxlan', interface_vxlan, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete vxlan interface
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan])
self.cli_commit()
def test_03_vpp_gre(self):
interface_gre = 'gre12'
interface_kernel = 'vpptun12'
new_interface_kernel = 'vpptun123'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '192.0.2.254'
kernel_address = '10.0.0.0'
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'source-address', source_address]
)
self.cli_set(
base_path + ['interfaces', 'gre', interface_gre, 'remote', remote_address]
)
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'kernel-interface', interface_kernel]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check gre interface
_, out = rc_cmd('sudo vppctl show gre tunnel')
required_str = f'[0] instance 12 src {source_address} dst {remote_address}'
self.assertIn(required_str, out)
# update gre interface
self.cli_set(
base_path
+ ['interfaces', 'gre', interface_gre, 'source-address', new_source_address]
)
self.cli_commit()
# check gre interface after update
_, out = rc_cmd('sudo vppctl show gre tunnel')
required_str = f'[0] instance 12 src {new_source_address} dst {remote_address}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete gre kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ ['interfaces', 'gre', interface_gre, 'kernel-interface', interface_kernel]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update gre kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'gre',
interface_gre,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete kernel interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete gre kernel-interface
self.cli_delete(
base_path + ['interfaces', 'gre', interface_gre, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete gre interface
self.cli_set(base_path + ['interfaces', 'gre', interface_gre])
self.cli_commit()
@unittest.skip("Skipping this test geneve index always is 0")
def test_04_vpp_geneve(self):
vni = '2'
# Must be 'geneve0' to pass smoketest
# As geneve interfaces cannot be named with "instance" suffix
interface_geneve = 'geneve0'
interface_kernel = f'vpptun{vni}'
new_interface_kernel = f'vpptun1{vni}'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '203.0.113.10'
kernel_address = '10.0.0.1'
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'source-address',
source_address,
]
)
self.cli_set(
base_path
+ ['interfaces', 'geneve', interface_geneve, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'geneve', interface_geneve, 'vni', vni])
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check geneve interface
_, out = rc_cmd('sudo vppctl show geneve tunnel')
required_str = f'[0] lcl {source_address} rmt {remote_address} vni {vni}'
self.assertIn(required_str, out)
# update geneve interface
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'source-address',
new_source_address,
]
)
self.cli_commit()
# check geneve interface after update
_, out = rc_cmd('sudo vppctl show geneve tunnel')
required_str = f'[0] lcl {new_source_address} rmt {remote_address} vni {vni}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete geneve kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update gemeve kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'geneve',
interface_geneve,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete geneve kernel-interface
self.cli_delete(
base_path + ['interfaces', 'geneve', interface_geneve, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete geneve interface
self.cli_set(base_path + ['interfaces', 'geneve', interface_geneve])
self.cli_commit()
def test_05_vpp_loopback(self):
interface_loopback = 'lo11'
interface_kernel = 'vpptun11'
new_interface_kernel = 'vpptun12'
kernel_address = '192.0.2.54'
self.cli_set(base_path + ['interfaces', 'loopback', interface_loopback])
self.cli_set(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/25']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check loopback interface
_, out = rc_cmd('sudo vppctl show interface loop11')
required_str = 'loop11'
self.assertIn(required_str, out)
# delete loopback kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update loopback kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'loopback',
interface_loopback,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete loopback kernel-interface
self.cli_delete(
base_path
+ ['interfaces', 'loopback', interface_loopback, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete loopback interface
self.cli_set(base_path + ['interfaces', 'loopback', interface_loopback])
self.cli_commit()
@unittest.skip("Skipping temporary bonding, sometimes get recursion T7117")
def test_06_vpp_bonding(self):
interface_bond = 'bond23'
interface_kernel = 'vpptun23'
hash = 'layer3+4'
mode = '802.3ad'
description = 'Interface-Bonding'
vlans = ['123', '456']
vlan_description = 'My-vlan-123'
self.cli_set(
base_path
+ [
'interfaces',
'bonding',
interface_bond,
'member',
'interface',
interface,
]
)
self.cli_set(
base_path + ['interfaces', 'bonding', interface_bond, 'hash-policy', hash]
)
self.cli_set(
base_path + ['interfaces', 'bonding', interface_bond, 'mode', mode]
)
# commit changes
self.cli_commit()
# Check for interface state "BondEthernet23 up"
_, out = rc_cmd('sudo vppctl show interface')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
self.assertRegex(
normalized_out,
r'BondEthernet23\s+\d+\s+up',
"Interface BondEthernet23 is not in the expected state 'up'.",
)
# set kernel interface
self.cli_set(
base_path
+ [
'interfaces',
'bonding',
interface_bond,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'description', description]
)
for vlan in vlans:
self.cli_set(
base_path
+ [
'kernel-interfaces',
interface_kernel,
'vif',
vlan,
'description',
vlan_description,
]
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}'))
current_alias = read_file(f'/sys/class/net/{interface_kernel}/ifalias')
vlan_alias = read_file(f'/sys/class/net/{interface_kernel}.{vlan}/ifalias')
self.assertEqual(current_alias, description)
self.assertEqual(vlan_alias, vlan_description)
# check bonding interface
_, out = rc_cmd('sudo vppctl show bond details')
required_enries = (
'BondEthernet23',
'mode: lacp',
'load balance: l34',
'number of active members: 0',
'number of members: 1',
f'{interface}',
'device instance: 0',
'interface id: 23',
)
for entry in required_enries:
self.assertIn(entry, out)
# check interface state
_, out = rc_cmd('sudo vppctl show interface')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Check for interface state "BondEthernet23 up"
self.assertRegex(
normalized_out,
r'BondEthernet23\s+\d+\s+up',
"Interface BondEthernet23 is not in the expected state 'up'.",
)
# delete vpp kernel-interface vlan
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel, 'vif'])
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}.{vlan}'))
# delete vpp kernel-interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete bonding kernel-interface
self.cli_delete(
base_path + ['interfaces', 'bonding', interface_bond, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete bonding interface
self.cli_delete(base_path + ['interfaces', 'bonding'])
self.cli_commit()
# check deleting bonding interface
_, out = rc_cmd('sudo vppctl show interface')
self.assertNotIn('BondEthernet23', out)
def test_07_vpp_bridge(self):
fake_member = 'eth2'
members = [interface]
interface_bridge = 'br10'
vni = '23'
interface_vxlan = f'vxlan{vni}'
source_address = '192.0.2.1'
remote_address = '192.0.2.254'
for member in members:
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
member,
]
)
# commit changes
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Perform assertions based on the normalized output
self.assertIn('BD-ID Index BSN Age(min)', normalized_out)
self.assertIn('10 1 0 off', normalized_out)
self.assertIn('Learning U-Forwrd UU-Flood Flooding', normalized_out)
self.assertIn('on on flood on', normalized_out)
self.assertIn('Interface If-idx ISN', normalized_out)
# Check Interface, If-idx, ISN
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
# Set non exist member
# expect raise ConfigErro
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
fake_member,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
fake_member,
]
)
# Add VXLAN to the bridge
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
self.cli_set(
base_path
+ [
'interfaces',
'bridge',
interface_bridge,
'member',
'interface',
interface_vxlan,
]
)
# commit changes
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
# Perform assertions based on the normalized output
self.assertIn('BD-ID Index BSN Age(min)', normalized_out)
self.assertIn('10 1 1 off', normalized_out)
self.assertIn('Learning U-Forwrd UU-Flood Flooding', normalized_out)
self.assertIn('on on flood on', normalized_out)
self.assertIn('Interface If-idx ISN', normalized_out)
# Check Interface, If-idx, ISN
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
self.assertRegex(out, r'\s*vxlan_tunnel23\s+\d+\s+\d+')
# Add check dependency ethernet => bridge
self.cli_set(
base_path + ['settings', 'interface', interface, 'dpdk-options', 'promisc']
)
self.cli_commit()
# check bridge interface
_, out = rc_cmd('sudo vppctl show bridge-domain 10 detail')
# Normalize the output for consistent whitespace
normalized_out = re.sub(r'\s+', ' ', out)
self.assertRegex(out, r'\s*eth1\s+\d+\s+\d+')
self.assertRegex(out, r'\s*vxlan_tunnel23\s+\d+\s+\d+')
def test_08_vpp_ipip(self):
interface_ipip = 'ipip12'
interface_kernel = 'vpptun12'
new_interface_kernel = 'vpptun123'
source_address = '192.0.2.1'
new_source_address = '192.0.2.2'
remote_address = '192.0.2.5'
kernel_address = '10.0.0.0'
self.cli_set(
base_path
+ ['interfaces', 'ipip', interface_ipip, 'source-address', source_address]
)
self.cli_set(
base_path + ['interfaces', 'ipip', interface_ipip, 'remote', remote_address]
)
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
interface_kernel,
]
)
self.cli_set(
base_path
+ ['kernel-interfaces', interface_kernel, 'address', f'{kernel_address}/31']
)
# commit changes
self.cli_commit()
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
current_address = get_address(interface_kernel)
self.assertEqual(kernel_address, current_address)
# check ipip interface
_, out = rc_cmd('sudo vppctl show ipip tunnel')
required_str = f'[0] instance 12 src {source_address} dst {remote_address}'
self.assertIn(required_str, out)
# update ipip interface
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'source-address',
new_source_address,
]
)
self.cli_commit()
# check ipip interface after update
_, out = rc_cmd('sudo vppctl show ipip tunnel')
required_str = f'[0] instance 12 src {new_source_address} dst {remote_address}'
self.assertIn(required_str, out)
self.assertTrue(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
self.assertEqual(kernel_address, current_address)
# delete ipip kernel-interface but do not delete 'vpp kernel-interface'
# expect raise ConfigError
self.cli_delete(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# update ipip kernel-interface but do not change 'vpp kernel-interface'
# expect raise ConfigError
self.cli_set(
base_path
+ [
'interfaces',
'ipip',
interface_ipip,
'kernel-interface',
new_interface_kernel,
]
)
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete kernel interface
self.cli_delete(base_path + ['kernel-interfaces', interface_kernel])
self.cli_commit()
# delete ipip kernel-interface
self.cli_delete(
base_path + ['interfaces', 'ipip', interface_ipip, 'kernel-interface']
)
self.cli_commit()
self.assertFalse(os.path.isdir(f'/sys/class/net/{interface_kernel}'))
# delete ipip interface
self.cli_set(base_path + ['interfaces', 'ipip', interface_ipip])
self.cli_commit()
def test_09_vpp_xconnect(self):
vni = '23'
interface_vxlan = f'vxlan{vni}'
interface_xconnect = f'xcon{vni}'
source_address = '192.0.2.1'
remote_address = '192.0.2.254'
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'source-address', source_address]
)
self.cli_set(
base_path
+ ['interfaces', 'vxlan', interface_vxlan, 'remote', remote_address]
)
self.cli_set(base_path + ['interfaces', 'vxlan', interface_vxlan, 'vni', vni])
# Add xconneect
self.cli_set(
base_path
+ [
'interfaces',
'xconnect',
interface_xconnect,
'member',
'interface',
interface,
]
)
# Cross connect interfaces require 2 interfaces
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(
base_path
+ [
'interfaces',
'xconnect',
interface_xconnect,
'member',
'interface',
interface_vxlan,
]
)
# commit changes
self.cli_commit()
# check interface mode
_, out = rc_cmd('sudo vppctl show mode')
required_str_list = [
f'l2 xconnect {interface} vxlan_tunnel{vni}',
f'l2 xconnect vxlan_tunnel{vni} {interface}',
]
for required_string in required_str_list:
self.assertIn(required_string, out)
# delete xconnect interface
self.cli_delete(base_path + ['interfaces', 'xconnect', interface_xconnect])
self.cli_commit()
# check delete xconnect interface
_, out = rc_cmd('sudo vppctl show mode')
for required_string in required_str_list:
self.assertNotIn(required_string, out)
def test_10_vpp_driver_options(self):
dpdk_options = {
'num-rx-desc': '512',
'num-tx-desc': '512',
'num-rx-queues': '3',
'num-tx-queues': '3',
}
base_interface_path = base_path + ['settings', 'interface', interface]
for option, value in dpdk_options.items():
self.cli_set(base_interface_path + ['dpdk-options', option, value])
# DPDK driver expect only dpdk-options and not xdp-options to be set
# expect raise ConfigError
self.cli_set(base_interface_path + ['xdp-options', 'no-syscall-lock'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
# delete xdp-options and apply commit
self.cli_delete(base_interface_path + ['xdp-options'])
self.cli_commit()
# check dpdk options in config file
config = read_file(VPP_CONF)
for option, value in dpdk_options.items():
self.assertIn(f'{option} {value}', config)
def test_11_vpp_cpu_settings(self):
main_core = '2'
workers = '2'
skip_cores = '1'
self.cli_set(base_path + ['settings', 'cpu', 'workers', workers])
# "cpu workers" reqiures main-core to be set
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', '99'])
# "cpu skip-cores" cannot be more than number of available CPUs - 1
# expect raise ConfigError
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_set(base_path + ['settings', 'cpu', 'skip-cores', skip_cores])
self.cli_commit()
config_entries = (
f'skip-cores {skip_cores}',
f'main-core {main_core}',
f'workers {workers}',
'dev 0000:00:00.0',
)
# Check configured options
config = read_file(VPP_CONF)
for config_entry in config_entries:
self.assertIn(config_entry, config)
+ def test_12_vpp_cpu_corelist_workers(self):
+ main_core = '0'
+ corelist_workers = ['1', '2-3']
+
+ for worker in corelist_workers:
+ self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', worker])
+
+ # "cpu corelist-workers" reqiures main-core to be set
+ # expect raise ConfigError
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base_path + ['settings', 'cpu', 'main-core', main_core])
+
+ # corelist-workers and workers cannot be used at the same time
+ # expect raise ConfigError
+ self.cli_set(base_path + ['settings', 'cpu', 'workers', '2'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['settings', 'cpu', 'workers'])
+
+ # verify corelist-workers are set not correctly
+ # expect raise ConfigError
+ self.cli_set(base_path + ['settings', 'cpu', 'corelist-workers', '99-101'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_delete(base_path + ['settings', 'cpu', 'corelist-workers', '99-101'])
+
+ self.cli_commit()
+
+ config_entries = (
+ f'main-core {main_core}',
+ f'corelist-workers {",".join(corelist_workers)}',
+ 'dev 0000:00:00.0',
+ )
+
+ # Check configured options
+ config = read_file(VPP_CONF)
+ for config_entry in config_entries:
+ self.assertIn(config_entry, config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/vpp.py b/src/conf_mode/vpp.py
index 5126b7c79..fe6436c15 100755
--- a/src/conf_mode/vpp.py
+++ b/src/conf_mode/vpp.py
@@ -1,647 +1,658 @@
#!/usr/bin/env python3
#
# Copyright (C) 2023-2024 VyOS Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from pathlib import Path
from psutil import virtual_memory
from pyroute2 import IPRoute
from vyos import ConfigError
from vyos import airbag
from vyos.base import Warning
from vyos.config import Config, config_dict_merge
from vyos.configdep import set_dependents, call_dependents
from vyos.configdict import node_changed, leaf_node_changed
from vyos.utils.cpu import get_core_count, get_available_cpus
from vyos.ifconfig import Section
from vyos.template import render
from vyos.utils.boot import boot_configuration_complete
from vyos.utils.process import call
from vyos.utils.system import sysctl_read, sysctl_apply
from vyos.vpp import VPPControl
from vyos.vpp import control_host
from vyos.vpp.config_deps import deps_xconnect_dict
from vyos.vpp.config_verify import verify_dev_driver
from vyos.vpp.config_filter import iface_filter_eth
from vyos.vpp.utils import EthtoolGDrvinfo
from vyos.vpp.configdb import JSONStorage
airbag.enable()
service_name = 'vpp'
service_conf = Path(f'/run/vpp/{service_name}.conf')
systemd_override = '/run/systemd/system/vpp.service.d/10-override.conf'
dependency_interface_type_map = {
'vpp_interfaces_bonding': 'bonding',
'vpp_interfaces_bridge': 'bridge',
'vpp_interfaces_ethernet': 'ethernet',
'vpp_interfaces_geneve': 'geneve',
'vpp_interfaces_gre': 'gre',
'vpp_interfaces_ipip': 'ipip',
'vpp_interfaces_loopback': 'loopback',
'vpp_interfaces_vxlan': 'vxlan',
'vpp_interfaces_xconnect': 'xconnect',
}
# dict of drivers that needs to be overrided
override_drivers: dict[str, str] = {
'hv_netvsc': 'uio_hv_generic',
'ena': 'vfio-pci',
}
# drivers that does not use PCIe addresses
not_pci_drv: list[str] = ['hv_netvsc']
def get_config(config=None):
# use persistent config to store interfaces data between executions
# this is required because some interfaces after they are connected
# to VPP is really hard or impossible to restore without knowing
# their original parameters (like IDs)
persist_config = JSONStorage('vpp_conf')
eth_ifaces_persist: dict[str, dict[str, str]] = persist_config.read(
'eth_ifaces', {}
)
if config:
conf = config
else:
conf = Config()
base = ['vpp']
base_settings = ['vpp', 'settings']
# find interfaces removed from VPP
effective_config = conf.get_config_dict(
base,
key_mangling=('-', '_'),
effective=True,
get_first_key=True,
no_tag_node_value_mangle=True,
)
xconn_members = deps_xconnect_dict(conf)
removed_ifaces = []
tmp = node_changed(conf, base_settings + ['interface'])
if tmp:
for removed_iface in tmp:
to_append = {
'iface_name': removed_iface,
'driver': effective_config['settings']['interface'][removed_iface][
'driver'
],
}
removed_ifaces.append(to_append)
# add an interface to a list of interfaces that need
# to be reinitialized after the commit
set_dependents('ethernet', conf, removed_iface)
if not conf.exists(base):
return {
'removed_ifaces': removed_ifaces,
'xconn_members': xconn_members,
'persist_config': eth_ifaces_persist,
}
config = conf.get_config_dict(
base,
get_first_key=True,
key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
)
# Get default values which we need to conditionally update into the
# dictionary retrieved.
default_values = conf.get_config_defaults(**config.kwargs, recursive=True)
# delete "xdp-options" from defaults if driver is DPDK
for iface, iface_config in config.get('settings', {}).get('interface', {}).items():
if iface_config.get('driver') == 'dpdk':
del default_values['settings']['interface'][iface]['xdp_options']
config = config_dict_merge(default_values, config)
# add running config
if effective_config:
config['effective'] = effective_config
if 'settings' in config:
if 'interface' in config['settings']:
for iface, iface_config in config['settings']['interface'].items():
# Driver must be configured to continue
if 'driver' not in iface_config:
raise ConfigError(
f'"driver" must be configured for {iface} interface!'
)
old_driver = leaf_node_changed(
conf, base_settings + ['interface', iface, 'driver']
)
# Get current kernel module, required for extra verification and
# logic for VMBus interfaces
config['settings']['interface'][iface]['kernel_module'] = (
EthtoolGDrvinfo(iface).driver
)
# filter unsupported config nodes
iface_filter_eth(conf, iface)
set_dependents('ethernet', conf, iface)
# Interfaces with changed driver should be removed/readded
if old_driver and old_driver[0] == 'dpdk':
removed_ifaces.append(
{
'iface_name': iface,
'driver': 'dpdk',
}
)
# Get PCI address or device ID
if iface_config['driver'] == 'dpdk':
if 'dpdk_options' not in iface_config:
iface_config['dpdk_options'] = {}
# Check in a persistent config first
id_from_persisten_conf = eth_ifaces_persist.get(iface, {}).get(
'dev_id'
)
if id_from_persisten_conf:
iface_config['dpdk_options']['dev_id'] = id_from_persisten_conf
else:
try:
iface_to_search = iface
if old_driver and old_driver[0] == 'xdp':
iface_to_search = f'defunct_{iface}'
iface_config['dpdk_options']['dev_id'] = (
control_host.get_dev_id(iface_to_search)
)
except Exception:
# Return empty address if all attempts failed
# We will catch this in verify()
iface_config['dpdk_options']['dev_id'] = ''
# prepare XDP interface parameters
if iface_config['driver'] == 'xdp':
xdp_api_params = {
'rxq_size': int(iface_config['xdp_options']['rx_queue_size']),
'txq_size': int(iface_config['xdp_options']['tx_queue_size']),
}
if iface_config['xdp_options']['num_rx_queues'] == 'all':
xdp_api_params['rxq_num'] = 0
else:
xdp_api_params['rxq_num'] = int(
iface_config['xdp_options']['num_rx_queues']
)
if 'zero-copy' in iface_config['xdp_options']:
xdp_api_params['mode'] = 'zero-copy'
if 'zero-copy' in iface_config['xdp_options']:
xdp_api_params['flags'] = 'no_syscall_lock'
iface_config['xdp_api_params'] = xdp_api_params
if removed_ifaces:
config['removed_ifaces'] = removed_ifaces
config['xconn_members'] = xconn_members
# Dependencies
for dependency, interface_type in dependency_interface_type_map.items():
# if conf.exists(base + ['interfaces', interface_type]):
if effective_config.get('interfaces', {}).get(interface_type):
for iface, iface_config in (
config.get('interfaces', {}).get(interface_type, {}).items()
):
# filter unsupported config nodes
if interface_type == 'ethernet':
iface_filter_eth(conf, iface)
set_dependents(dependency, conf, iface)
# Save important info about all interfaces that cannot be retrieved later
# Add new interfaces (only if they are first time seen in a config)
for iface, iface_config in config.get('settings', {}).get('interface', {}).items():
if iface not in effective_config.get('settings', {}).get('interface', {}):
eth_ifaces_persist[iface] = {
'original_driver': config['settings']['interface'][iface][
'kernel_module'
],
}
eth_ifaces_persist[iface]['bus_id'] = control_host.get_bus_name(iface)
eth_ifaces_persist[iface]['dev_id'] = control_host.get_dev_id(iface)
# Return to config dictionary
config['persist_config'] = eth_ifaces_persist
return config
def convert_to_int(val):
rates = {
'K': 1024,
'M': 1024**2,
'G': 1024**3,
}
try:
return int(val)
except ValueError:
return int(val[:-1]) * rates[val[-1]]
def verify_memory(settings):
memory_available: int = virtual_memory().available
cpus: int = get_core_count()
nr_hugepages = int(settings['host_resources']['nr_hugepages'])
hugepages_memory = nr_hugepages * 2 * 1024**2
memory_required = hugepages_memory
buffers_per_numa = int(settings.get('buffers', {}).get('buffers_per_numa', 16384))
data_size = int(settings.get('buffers', {}).get('data_size', 2048))
buffers_memory = buffers_per_numa * data_size * cpus
memory_required += buffers_memory
netlink_buffer_size = int(
settings.get('lcp', {}).get('netlink', {}).get('rx_buffer_size', 212992)
)
memory_required += netlink_buffer_size
memory_main_heap = convert_to_int(
settings.get('memory', {}).get('main_heap_size', '1G')
)
memory_required += memory_main_heap
statseg_size = convert_to_int(settings.get('statseg', {}).get('size', '96M'))
memory_required += statseg_size
if memory_available < memory_required:
raise ConfigError(
'Not enough free memory to start VPP:\n'
f'available: {round(memory_available / 1024 ** 3, 1)}GB\n'
f'required: {round(memory_required / 1024 ** 3, 1)}GB'
)
def verify(config):
# bail out early - looks like removal from running config
if not config or ('removed_ifaces' in config and 'settings' not in config):
return None
if 'settings' not in config:
raise ConfigError('"settings interface" is required but not set!')
if 'interface' not in config['settings']:
raise ConfigError('"settings interface" is required but not set!')
# check if Ethernet interfaces exist
ethernet_ifaces = Section.interfaces('ethernet')
for iface in config['settings']['interface'].keys():
if iface not in ethernet_ifaces:
raise ConfigError(f'Interface {iface} does not exist or is not Ethernet!')
# ensure DPDK/XDP settings are properly configured
for iface, iface_config in config['settings']['interface'].items():
# check if selected driver is supported, but only for new interfaces
if iface not in config.get('effective', {}).get('settings', {}).get(
'interface', {}
):
if not verify_dev_driver(iface, iface_config['driver']):
raise ConfigError(
f'Driver {iface_config["driver"]} is not compatible with interface {iface}!'
)
if iface_config['driver'] == 'xdp' and 'xdp_options' in iface_config:
if iface_config['xdp_options']['num_rx_queues'] != 'all':
Warning(f'Not all RX queues will be connected to VPP for {iface}!')
if iface_config['driver'] == 'xdp' and 'dpdk_options' in iface_config:
raise ConfigError('DPDK options are not applicable for XDP driver!')
if iface_config['driver'] == 'dpdk' and 'xdp_options' in iface_config:
raise ConfigError('XDP options are not applicable for DPDK driver!')
# check GRE tunnels as part of the bridge, only tunnel-type teb is allowed
# set vpp interfaces bridge br1 member interface gre1
# set vpp interfaces gre gre1 tunnel-type teb
if 'interfaces' in config:
if 'bridge' in config['interfaces']:
for iface, iface_config in config['interfaces']['bridge'].items():
if 'member' in iface_config:
for member in iface_config['member'].get('interface', []):
if member.startswith('gre'):
if (
'gre' in config['interfaces']
and config['interfaces']['gre']
.get(member, {})
.get('tunnel_type')
!= 'teb'
):
raise ConfigError(
f'Only tunnel-type teb is allowed for GRE interfaces in bridge {iface}'
)
# Only one multipoint GRE tunnel is allowed from the same source address
# set vpp interfaces gre gre0 mode 'point-to-multipoint'
# set vpp interfaces gre gre0 remote '0.0.0.0'
# set vpp interfaces gre gre0 source-address '192.0.2.1'
# set vpp interfaces gre gre1 mode 'point-to-multipoint'
# set vpp interfaces gre gre1 remote '0.0.0.0'
# set vpp interfaces gre gre1 source-address '192.0.2.1'
if 'gre' in config['interfaces']:
for iface, iface_config in config['interfaces']['gre'].items():
if iface_config['mode'] == 'point-to-multipoint':
for other_iface, other_iface_config in config['interfaces'][
'gre'
].items():
if (
other_iface_config['mode'] == 'point-to-multipoint'
and other_iface_config['source_address']
== iface_config['source_address']
and iface != other_iface
):
raise ConfigError(
'Only one multipoint GRE tunnel is allowed from the same source address'
)
if 'cpu' in config['settings']:
if (
'corelist_workers' in config['settings']['cpu']
or 'workers' in config['settings']['cpu']
) and 'main_core' not in config['settings']['cpu']:
raise ConfigError('"cpu main-core" is required but not set!')
+ if (
+ 'corelist_workers' in config['settings']['cpu']
+ and 'workers' in config['settings']['cpu']
+ ):
+ raise ConfigError(
+ '"cpu corelist-workers" and "cpu workers" cannot be used at the same time!'
+ )
+
cpus = int(get_core_count())
skip_cores = 0
if 'skip_cores' in config['settings']['cpu']:
skip_cores = int(config['settings']['cpu']['skip_cores'])
# the number of skipped cores should not be more than all CPUs - 1 (for main core)
if skip_cores > cpus - 1:
raise ConfigError(
f'The system does not have enough available CPUs to skip '
f'(reduce "cpu skip-cores" to {cpus - 1} or less)'
)
if 'workers' in config['settings']['cpu']:
# number of worker threads must be not more than
# available CPUs in the system - 1 for main thread - number of skipped cores
# or - 1 (at least) for system processes
workers = int(config['settings']['cpu']['workers'])
available_workers = cpus - 1 - (skip_cores or 1)
if workers > available_workers:
raise ConfigError(
f'The system does not have enough CPUs for {workers} VPP workers '
f'(reduce to {available_workers} or less)'
)
cpus_available = list(map(lambda el: el['cpu'], get_available_cpus()))
# available CPUs are all CPUs without first N skipped cores that will not be used
cpus_available = cpus_available[skip_cores:]
if 'main_core' in config['settings']['cpu']:
main_core = int(config['settings']['cpu']['main_core'])
if main_core not in cpus_available:
raise ConfigError(f'"cpu main-core {main_core}" is not available!')
# CPU main-core must be not included to corelist-workers
if config.get('settings').get('cpu', {}).get('corelist_workers'):
corelist_workers = config['settings']['cpu']['corelist_workers']
all_core_numbers = []
for worker_range in corelist_workers:
core_numbers = worker_range.split('-')
if int(core_numbers[0]) > int(core_numbers[-1]):
raise ConfigError(
f'Range for "cpu corelist-workers {worker_range}" is not correct'
)
all_core_numbers.extend(
range(int(core_numbers[0]), int(core_numbers[-1]) + 1)
)
if main_core in all_core_numbers:
raise ConfigError(
f'"cpu main-core {main_core}" must not be included in the corelist-workers!'
)
+ if not all(el in cpus_available for el in all_core_numbers):
+ raise ConfigError('"cpu corelist-workers" is not correct')
+
verify_memory(config['settings'])
# Check if deleted interfaces are not xconnect memebrs
for iface_config in config.get('removed_ifaces', []):
if iface_config['iface_name'] in config.get('xconn_members', {}):
raise ConfigError(
f'Interface {iface_config["iface_name"]} is an xconnect member and cannot be removed'
)
def generate(config):
if not config or ('removed_ifaces' in config and 'settings' not in config):
# Remove old config and return
service_conf.unlink(missing_ok=True)
return None
render(service_conf, 'vpp/startup.conf.j2', config['settings'])
render(systemd_override, 'vpp/override.conf.j2', config)
# apply sysctl values
# default: https://github.com/FDio/vpp/blob/v23.10/src/vpp/conf/80-vpp.conf
sysctl_config: dict[str, str] = {
'vm.nr_hugepages': config['settings']['host_resources']['nr_hugepages'],
'vm.max_map_count': config['settings']['host_resources']['max_map_count'],
'vm.hugetlb_shm_group': '0',
'kernel.shmmax': config['settings']['host_resources']['shmmax'],
}
# we do not want to lower current values
for sysctl_key, sysctl_value in sysctl_config.items():
# perform check only for quantitative params
if sysctl_key == 'vm.hugetlb_shm_group':
pass
current_value = sysctl_read(sysctl_key)
if int(current_value) > int(sysctl_value):
sysctl_config[sysctl_key] = current_value
if not sysctl_apply(sysctl_config):
raise ConfigError('Cannot configure sysctl parameters for VPP')
return None
def apply(config):
# Open persistent config
# It is required for operations with interfaces
persist_config = JSONStorage('vpp_conf')
if not config or ('removed_ifaces' in config and 'settings' not in config):
# Cleanup persistent config
persist_config.delete()
# And stop the service
call(f'systemctl stop {service_name}.service')
else:
# Some interfaces required extra preparation before VPP can be started
if 'settings' in config and 'interface' in config.get('settings'):
for iface, iface_config in config['settings']['interface'].items():
if iface_config['driver'] == 'dpdk':
# ena interfaces require noiommu mode
if iface_config['kernel_module'] == 'ena':
control_host.unsafe_noiommu_mode(True)
if iface_config['kernel_module'] in override_drivers:
control_host.override_driver(
config['persist_config'][iface]['bus_id'],
config['persist_config'][iface]['dev_id'],
override_drivers[iface_config['kernel_module']],
)
call('systemctl daemon-reload')
call(f'systemctl restart {service_name}.service')
# Initialize interfaces removed from VPP
for iface in config.get('removed_ifaces', []):
# DPDK - rescan PCI to use a proper driver
if (
iface['driver'] == 'dpdk'
and config['persist_config'][iface['iface_name']]['original_driver']
not in not_pci_drv
):
control_host.pci_rescan(
config['persist_config'][iface['iface_name']]['dev_id']
)
# rename to the proper name
iface_new_name: str = control_host.get_eth_name(
config['persist_config'][iface['iface_name']]['dev_id']
)
control_host.rename_iface(iface_new_name, iface['iface_name'])
# XDP - rename an interface , disable promisc and XDP
if iface['driver'] == 'xdp':
control_host.set_promisc(f'defunct_{iface["iface_name"]}', 'off')
control_host.rename_iface(
f'defunct_{iface["iface_name"]}', iface['iface_name']
)
control_host.xdp_remove(iface['iface_name'])
# Rename Mellanox NIC to a normal name
try:
if (
control_host.get_eth_driver(f'defunct_{iface["iface_name"]}')
== 'mlx5_core'
):
control_host.rename_iface(
f'defunct_{iface["iface_name"]}', iface['iface_name']
)
except FileNotFoundError:
pass
# Replace a driver with original for VMBus interfaces and rename it
if (
iface['driver'] == 'dpdk'
and config['persist_config'][iface['iface_name']]['original_driver']
in override_drivers
):
control_host.override_driver(
config['persist_config'][iface['iface_name']]['bus_id'],
config['persist_config'][iface['iface_name']]['dev_id'],
)
iface_new_name: str = control_host.get_eth_name(
config['persist_config'][iface['iface_name']]['dev_id']
)
control_host.rename_iface(iface_new_name, iface['iface_name'])
# Remove what is not in the config anymore
if iface['iface_name'] not in config.get('settings', {}).get('interface', {}):
del config['persist_config'][iface['iface_name']]
if 'settings' in config and 'interface' in config.get('settings'):
# connect to VPP
# must be performed multiple attempts because API is not available
# immediately after the service restart
vpp_control = VPPControl(attempts=20, interval=500)
# preconfigure LCP plugin
if 'ignore_kernel_routes' in config.get('settings', {}).get('lcp', {}):
vpp_control.cli_cmd('lcp param route-no-paths off')
else:
vpp_control.cli_cmd('lcp param route-no-paths on')
# add interfaces
iproute = IPRoute()
for iface, iface_config in config['settings']['interface'].items():
# promisc option for DPDK interfaces
if iface_config['driver'] == 'dpdk':
if 'promisc' in iface_config['dpdk_options']:
if_index = vpp_control.get_sw_if_index(iface)
vpp_control.api.sw_interface_set_promisc(
sw_if_index=if_index, promisc_on=True
)
# add XDP interfaces
if iface_config['driver'] == 'xdp':
control_host.rename_iface(iface, f'defunct_{iface}')
vpp_control.xdp_iface_create(
host_if=f'defunct_{iface}',
name=iface,
**iface_config['xdp_api_params'],
)
# replicate MAC address of a real interface
real_mac = control_host.get_eth_mac(f'defunct_{iface}')
vpp_control.set_iface_mac(iface, real_mac)
if 'promisc' in iface_config['xdp_options']:
control_host.set_promisc(f'defunct_{iface}', 'on')
control_host.set_status(f'defunct_{iface}', 'up')
# Rename Mellanox interfaces to hide them and create LCP properly
if (
iface in Section.interfaces()
and control_host.get_eth_driver(iface) == 'mlx5_core'
):
control_host.rename_iface(iface, f'defunct_{iface}')
control_host.set_status(f'defunct_{iface}', 'up')
# Create lcp
if iface not in Section.interfaces():
vpp_control.lcp_pair_add(iface, iface)
# Set rx-mode
rx_mode = iface_config.get('rx_mode')
if rx_mode:
# to hardware side
vpp_control.iface_rxmode(iface, rx_mode)
# to kernel side
lcp_name = vpp_control.lcp_pair_find(vpp_name_hw=iface).get(
'vpp_name_kernel'
)
vpp_control.iface_rxmode(lcp_name, rx_mode)
# For unknown reasons, if multiple interfaces later try to be
# initialized by configuration scripts, some of them may stuck
# in an endless UP/DOWN loop
# We found two workarounds - pause initialization (requires
# main code modifications).
# And this one
dev_index = iproute.link_lookup(ifname=iface)[0]
iproute.link('set', index=dev_index, state='up')
# Syncronize routes via LCP
vpp_control.lcp_resync()
# Save persistent config
if 'persist_config' in config and config['persist_config']:
persist_config.write('eth_ifaces', config['persist_config'])
# reinitialize interfaces, but not during the first boot
if boot_configuration_complete():
call_dependents()
if __name__ == '__main__':
try:
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
exit(1)

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 6, 7:52 AM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4653268
Default Alt Text
(63 KB)

Event Timeline