Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F119063057
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
63 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Nov 6, 7:52 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4653268
Default Alt Text
(63 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment