SRIOV: Support referring interface using PF name and VF ID

Kernel function `dev_valid_name()` indicate `:` is not allowed character
for interface name. Hence we introduce special interface name format
when referring a SRIOV VF interface without knowing its real interface
name.

    sriov:<pf_name>:<vf_id>

You may use this SRIOV VF naming schema for:
 * Top interface name
 * Bond port name
 * Linux bridge port name
 * OVS bridge port name
 * OVS bond port name

Both Unit test cases and integration test cases are included.
Integration test cases are tested on Mellanox MT27710(mlx5).

Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
Gris Ge 2022-09-26 16:28:21 +08:00 committed by Fernando Fernández Mancera
parent 4f737f3d8d
commit 13cec24117
10 changed files with 985 additions and 193 deletions

View File

@ -50,6 +50,29 @@ impl BondInterface {
.map(|ports| ports.remove(index));
}
}
pub(crate) fn change_port_name(
&mut self,
origin_name: &str,
new_name: String,
) {
if let Some(index) = self
.bond
.as_ref()
.and_then(|bond_conf| bond_conf.port.as_ref())
.and_then(|ports| {
ports.iter().position(|port_name| port_name == origin_name)
})
{
if let Some(ports) = self
.bond
.as_mut()
.and_then(|bond_conf| bond_conf.port.as_mut())
{
ports[index] = new_name;
}
}
}
}
impl BondConfig {

View File

@ -236,4 +236,18 @@ impl Interface {
Self::Unknown(_) | Self::Dummy(_) | Self::OvsInterface(_) => (),
}
}
pub(crate) fn change_port_name(
&mut self,
org_port_name: &str,
new_port_name: String,
) {
if let Interface::LinuxBridge(iface) = self {
iface.change_port_name(org_port_name, new_port_name);
} else if let Interface::OvsBridge(iface) = self {
iface.change_port_name(org_port_name, new_port_name);
} else if let Interface::Bond(iface) = self {
iface.change_port_name(org_port_name, new_port_name);
}
}
}

View File

@ -121,6 +121,7 @@ impl Interfaces {
&ignored_user_ifaces,
);
cur_clone.remove_unknown_type_port();
self_clone.resolve_sriov_reference(pre_apply_current)?;
for iface in self_clone.to_vec() {
if iface.is_absent() || (iface.is_virtual() && iface.is_down()) {

View File

@ -126,6 +126,25 @@ impl LinuxBridgeInterface {
}
false
}
pub(crate) fn change_port_name(
&mut self,
origin_name: &str,
new_name: String,
) {
if let Some(port_conf) = self
.bridge
.as_mut()
.and_then(|br_conf| br_conf.port.as_mut())
.and_then(|port_confs| {
port_confs
.iter_mut()
.find(|port_conf| port_conf.name == origin_name)
})
{
port_conf.name = new_name;
}
}
}
impl LinuxBridgeConfig {

View File

@ -126,6 +126,10 @@ impl NetworkState {
.interfaces
.resolve_unknown_ifaces(&cur_net_state.interfaces)?;
desire_state_to_apply
.interfaces
.resolve_sriov_reference(&cur_net_state.interfaces)?;
let (add_net_state, chg_net_state, del_net_state) =
desire_state_to_apply.gen_state_for_apply(&cur_net_state)?;
@ -160,7 +164,7 @@ impl NetworkState {
// state instead of
// current,
&cur_net_state,
self,
&desire_state_to_apply,
&checkpoint,
self.memory_only,
)?;

View File

@ -153,6 +153,63 @@ impl OvsBridgeInterface {
br_ports.retain(|p| p.name.as_str() != port_name)
}
}
pub(crate) fn change_port_name(
&mut self,
origin_name: &str,
new_name: String,
) {
if let Some(index) = self
.bridge
.as_ref()
.and_then(|br_conf| br_conf.ports.as_ref())
.and_then(|ports| {
ports.iter().position(|port| port.name == origin_name)
})
{
if let Some(ports) = self
.bridge
.as_mut()
.and_then(|br_conf| br_conf.ports.as_mut())
{
ports[index].name = new_name;
}
} else if let Some(index) = self
.bridge
.as_ref()
.and_then(|br_conf| br_conf.ports.as_ref())
.and_then(|ports| {
ports.iter().position(|port_conf| {
port_conf
.bond
.as_ref()
.and_then(|bond_conf| bond_conf.ports.as_ref())
.map(|bond_port_confs| {
bond_port_confs
.iter()
.any(|bond_conf| bond_conf.name == origin_name)
})
.unwrap_or_default()
})
})
{
if let Some(bond_port_confs) = self
.bridge
.as_mut()
.and_then(|br_conf| br_conf.ports.as_mut())
.and_then(|ports| ports.get_mut(index))
.and_then(|port_conf| port_conf.bond.as_mut())
.and_then(|bond_conf| bond_conf.ports.as_mut())
{
for bond_port_conf in bond_port_confs {
if bond_port_conf.name == origin_name {
bond_port_conf.name = new_name;
break;
}
}
}
}
}
}
impl OvsBridgeBondConfig {

View File

@ -4,6 +4,9 @@ use crate::{
ErrorKind, Interface, InterfaceType, Interfaces, NmstateError, SrIovConfig,
};
const SRIOV_VF_NAMING_SEPERATOR: char = ':';
const SRIOV_VF_NAMING_PREFIX: &str = "sriov:";
impl SrIovConfig {
pub(crate) fn update(&mut self, other: Option<&SrIovConfig>) {
if let Some(other) = other {
@ -91,3 +94,182 @@ impl SrIovConfig {
Ok(())
}
}
impl Interfaces {
pub(crate) fn resolve_sriov_reference(
&mut self,
current: &Self,
) -> Result<(), NmstateError> {
self.resolve_sriov_reference_iface_name(current)?;
self.resolve_sriov_reference_port_name(current)?;
Ok(())
}
fn resolve_sriov_reference_iface_name(
&mut self,
current: &Self,
) -> Result<(), NmstateError> {
let mut changed_iface_names: Vec<String> = Vec::new();
for iface in self
.kernel_ifaces
.values_mut()
.filter(|i| i.iface_type() == InterfaceType::Ethernet)
{
if let Some((pf_name, vf_id)) = parse_sriov_vf_naming(iface.name())?
{
if let Some(vf_iface_name) =
get_sriov_vf_iface_name(current, pf_name, vf_id)
{
changed_iface_names.push(iface.name().to_string());
log::info!(
"SR-IOV VF {} resolved to interface name {}",
iface.name(),
vf_iface_name
);
iface.base_iface_mut().name = vf_iface_name;
} else {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Failed to find SR-IOV VF interface name for {}",
iface.name()
),
);
log::error!("{}", e);
return Err(e);
}
}
}
for changed_iface_name in changed_iface_names {
if let Some(iface) = self.kernel_ifaces.remove(&changed_iface_name)
{
if self.kernel_ifaces.get(iface.name()).is_some() {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"SR-IOV VF name {} has been resolved as interface \
{}, but it is already defined in desire state",
changed_iface_name,
iface.name()
),
);
log::error!("{}", e);
return Err(e);
}
self.kernel_ifaces.insert(iface.name().to_string(), iface);
}
}
Ok(())
}
fn resolve_sriov_reference_port_name(
&mut self,
current: &Self,
) -> Result<(), NmstateError> {
// pending_changes:
// Vec<(ctrl_name, ctrl_iface_type, origin_name, new_name)>
let mut pending_changes = Vec::new();
for iface in self
.kernel_ifaces
.values()
.chain(self.user_ifaces.values())
.filter(|i| i.is_controller())
{
let ports = match iface.ports() {
Some(p) => p,
None => continue,
};
for port in ports {
if let Some((pf_name, vf_id)) = parse_sriov_vf_naming(port)? {
if let Some(vf_iface_name) =
get_sriov_vf_iface_name(current, pf_name, vf_id)
{
log::info!(
"SR-IOV VF {} resolved to interface name {}",
port,
vf_iface_name
);
pending_changes.push((
iface.name().to_string(),
iface.iface_type(),
port.to_string(),
vf_iface_name.to_string(),
));
}
}
}
}
for (ctrl, ctrl_iface_type, origin_name, new_name) in pending_changes {
if let Some(iface) = self.get_iface_mut(&ctrl, ctrl_iface_type) {
iface.change_port_name(origin_name.as_str(), new_name);
}
}
Ok(())
}
}
fn parse_sriov_vf_naming(
iface_name: &str,
) -> Result<Option<(&str, u32)>, NmstateError> {
if iface_name.starts_with(SRIOV_VF_NAMING_PREFIX) {
let names: Vec<&str> =
iface_name.split(SRIOV_VF_NAMING_SEPERATOR).collect();
if names.len() == 3 {
match names[2].parse::<u32>() {
Ok(vf_id) => Ok(Some((names[1], vf_id))),
Err(e) => {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Invalid SR-IOV VF ID in {}, correct format \
is 'sriov:<pf_name>:<vf_id>', error: {}",
iface_name, e
),
);
log::error!("{}", e);
Err(e)
}
}
} else {
let e = NmstateError::new(
ErrorKind::InvalidArgument,
format!(
"Invalid SR-IOV VF name {}, correct format is \
'sriov:<pf_name>:<vf_id>'",
iface_name,
),
);
log::error!("{}", e);
Err(e)
}
} else {
Ok(None)
}
}
pub(crate) fn get_sriov_vf_iface_name(
current: &Interfaces,
pf_name: &str,
vf_id: u32,
) -> Option<String> {
if let Some(Interface::Ethernet(pf_iface)) =
current.get_iface(pf_name, InterfaceType::Ethernet)
{
if let Some(vfs) = pf_iface
.ethernet
.as_ref()
.and_then(|e| e.sr_iov.as_ref())
.and_then(|s| s.vfs.as_ref())
{
for vf in vfs {
if vf.id == vf_id {
if !vf.iface_name.is_empty() {
return Some(vf.iface_name.clone());
}
break;
}
}
}
}
None
}

View File

@ -1,6 +1,7 @@
use crate::{
unit_tests::testlib::new_eth_iface, EthernetConfig, Interface, Interfaces,
SrIovConfig, SrIovVfConfig,
unit_tests::testlib::new_eth_iface, BridgePortVlanMode, ErrorKind,
EthernetConfig, Interface, InterfaceType, Interfaces, SrIovConfig,
SrIovVfConfig,
};
#[test]
@ -75,3 +76,371 @@ fn test_ignore_sriov_if_not_desired() {
desired.verify(&Interfaces::new(), &current).unwrap();
}
fn gen_sriov_current_ifaces() -> Interfaces {
let mut current = serde_yaml::from_str::<Interfaces>(
r#"---
- name: eth1
type: ethernet
state: up
ethernet:
sr-iov:
total-vfs: 2
vfs:
- id: 0
- id: 1
- name: eth1v0
type: ethernet
state: up
- name: eth1v1
type: ethernet
state: up
"#,
)
.unwrap();
let iface = current.kernel_ifaces.get_mut("eth1").unwrap();
if let Interface::Ethernet(eth_iface) = iface {
eth_iface
.ethernet
.as_mut()
.unwrap()
.sr_iov
.as_mut()
.unwrap()
.vfs
.as_mut()
.unwrap()[0]
.iface_name = "eth1v0".to_string();
eth_iface
.ethernet
.as_mut()
.unwrap()
.sr_iov
.as_mut()
.unwrap()
.vfs
.as_mut()
.unwrap()[1]
.iface_name = "eth1v1".to_string();
}
current
}
#[test]
fn test_resolve_sriov_name() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: sriov:eth1:0
type: ethernet
state: up
mtu: 1280
- name: sriov:eth1:1
type: ethernet
state: up
mtu: 1281
"#,
)
.unwrap();
desired.resolve_sriov_reference(&current).unwrap();
let vf0_iface = desired
.get_iface("eth1v0", InterfaceType::Ethernet)
.unwrap();
let vf1_iface = desired
.get_iface("eth1v1", InterfaceType::Ethernet)
.unwrap();
assert_eq!(vf0_iface.base_iface().mtu, Some(1280));
assert_eq!(vf1_iface.base_iface().mtu, Some(1281));
}
#[test]
fn test_resolve_sriov_name_duplicate() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: sriov:eth1:0
type: ethernet
state: up
mtu: 1280
- name: eth1v0
type: ethernet
state: up
mtu: 1281
"#,
)
.unwrap();
let result = desired.resolve_sriov_reference(&current);
assert!(result.is_err());
if let Err(e) = result {
assert_eq!(e.kind(), ErrorKind::InvalidArgument);
}
}
#[test]
fn test_verify_sriov_name() {
let current = gen_sriov_current_ifaces();
let desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: sriov:eth1:0
type: ethernet
state: up
"#,
)
.unwrap();
desired.verify(&current, &current).unwrap();
}
#[test]
fn test_resolve_sriov_port_name_linux_bridge() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: br0
type: linux-bridge
state: up
bridge:
port:
- name: sriov:eth1:1
vlan:
mode: access
tag: 305
"#,
)
.unwrap();
desired.resolve_sriov_reference(&current).unwrap();
if let Interface::LinuxBridge(br_iface) = desired
.get_iface("br0", InterfaceType::LinuxBridge)
.unwrap()
{
let port_confs =
br_iface.bridge.as_ref().unwrap().port.as_ref().unwrap();
assert_eq!(port_confs.len(), 1);
assert_eq!(port_confs[0].name, "eth1v1".to_string());
assert_eq!(
port_confs[0].vlan.as_ref().unwrap().mode.unwrap(),
BridgePortVlanMode::Access
);
assert_eq!(port_confs[0].vlan.as_ref().unwrap().tag.unwrap(), 305);
} else {
panic!("Failed to find expected bridge interface br0");
}
}
#[test]
fn test_resolve_sriov_port_name_bond() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: bond0
type: bond
state: up
link-aggregation:
mode: balance-rr
port:
- sriov:eth1:1
- sriov:eth1:0
"#,
)
.unwrap();
desired.resolve_sriov_reference(&current).unwrap();
let bond_iface = desired.get_iface("bond0", InterfaceType::Bond).unwrap();
let ports = bond_iface.ports().unwrap();
assert_eq!(ports.len(), 2);
assert_eq!(ports, vec!["eth1v1", "eth1v0"]);
}
#[test]
fn test_resolve_sriov_port_name_ovs_bridge() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: sriov:eth1:0
- name: sriov:eth1:1
"#,
)
.unwrap();
desired.resolve_sriov_reference(&current).unwrap();
let br_iface = desired
.get_iface("ovs-br0", InterfaceType::OvsBridge)
.unwrap();
let ports = br_iface.ports().unwrap();
assert_eq!(ports.len(), 2);
assert_eq!(ports, vec!["eth1v0", "eth1v1"]);
}
#[test]
fn test_resolve_sriov_port_name_ovs_bond() {
let current = gen_sriov_current_ifaces();
let mut desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: bond1
link-aggregation:
mode: balance-slb
port:
- name: eth2
- name: sriov:eth1:1
"#,
)
.unwrap();
desired.resolve_sriov_reference(&current).unwrap();
let br_iface = desired
.get_iface("ovs-br0", InterfaceType::OvsBridge)
.unwrap();
let ports = br_iface.ports().unwrap();
assert_eq!(ports.len(), 2);
assert_eq!(ports, vec!["eth2", "eth1v1"]);
}
#[test]
fn test_verify_sriov_port_name_linux_bridge() {
let pre_apply_current = gen_sriov_current_ifaces();
let desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: br0
type: linux-bridge
state: up
bridge:
port:
- name: sriov:eth1:1
vlan:
mode: access
tag: 305
"#,
)
.unwrap();
let mut current = gen_sriov_current_ifaces();
current.push(
serde_yaml::from_str::<Interface>(
r#"---
name: br0
type: linux-bridge
state: up
bridge:
port:
- name: eth1v1
vlan:
mode: access
tag: 305
"#,
)
.unwrap(),
);
desired.verify(&pre_apply_current, &current).unwrap();
}
#[test]
fn test_verify_sriov_port_name_bond() {
let pre_apply_current = gen_sriov_current_ifaces();
let desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: bond0
type: bond
state: up
link-aggregation:
mode: balance-rr
port:
- sriov:eth1:1
"#,
)
.unwrap();
let mut current = gen_sriov_current_ifaces();
current.push(
serde_yaml::from_str::<Interface>(
r#"---
name: bond0
type: bond
state: up
link-aggregation:
mode: balance-rr
port:
- eth1v1
"#,
)
.unwrap(),
);
desired.verify(&pre_apply_current, &current).unwrap();
}
#[test]
fn test_verify_sriov_port_name_ovs_bridge() {
let pre_apply_current = gen_sriov_current_ifaces();
let desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: sriov:eth1:1
"#,
)
.unwrap();
let mut current = gen_sriov_current_ifaces();
current.push(
serde_yaml::from_str::<Interface>(
r#"---
name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: eth1v1
"#,
)
.unwrap(),
);
desired.verify(&pre_apply_current, &current).unwrap();
}
#[test]
fn test_verify_sriov_port_name_ovs_bond() {
let pre_apply_current = gen_sriov_current_ifaces();
let desired = serde_yaml::from_str::<Interfaces>(
r#"---
- name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: bond1
link-aggregation:
mode: balance-slb
port:
- name: eth2
- name: sriov:eth1:1
"#,
)
.unwrap();
let mut current = gen_sriov_current_ifaces();
current.push(
serde_yaml::from_str::<Interface>(
r#"---
name: ovs-br0
type: ovs-bridge
state: up
bridge:
port:
- name: bond1
link-aggregation:
mode: balance-slb
port:
- name: eth1v1
- name: eth2
"#,
)
.unwrap(),
);
desired.verify(&pre_apply_current, &current).unwrap();
}

View File

@ -1,39 +1,35 @@
#
# Copyright (c) 2019-2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: LGPL-2.1-or-later
import copy
import os
import pytest
import libnmstate
from libnmstate.schema import Bond
from libnmstate.schema import Ethernet
from libnmstate.schema import Interface
from libnmstate.schema import InterfaceState
from libnmstate.schema import InterfaceType
from libnmstate.schema import LinuxBridge
from libnmstate.schema import OVSBridge
from .testlib import assertlib
from .testlib import statelib
from .testlib.bondlib import bond_interface
from .testlib.bridgelib import add_port_to_bridge
from .testlib.bridgelib import create_bridge_subtree_state
from .testlib.bridgelib import linux_bridge
from .testlib.ovslib import ovs_bridge
from .testlib.ovslib import ovs_bridge_bond
MAC1 = "00:11:22:33:44:55"
MAC2 = "00:11:22:33:44:66"
MAC3 = "00:11:22:33:44:FF"
MAC_MIX_CASE = "00:11:22:33:44:Ff"
TEST_BOND = "test-bond0"
TEST_BRIDGE = "test-br0"
TEST_OVS_IFACE = "test-ovs0"
VF0_CONF = {
Ethernet.SRIOV.VFS.ID: 0,
@ -54,6 +50,12 @@ def _test_nic_name():
return os.environ.get("TEST_REAL_NIC")
def find_vf_iface_name(pf_name, vf_id):
return os.listdir(
f"/sys/class/net/{pf_name}/device/virtfn{vf_id}/net"
).pop()
@pytest.fixture
def disable_sriov():
pf_name = _test_nic_name()
@ -121,189 +123,288 @@ def sriov_iface_vf(disable_sriov):
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_with_no_vfs_config(sriov_interface):
assertlib.assert_state_match(sriov_interface)
class TestSrIov:
def test_sriov_with_no_vfs_config(self, sriov_interface):
assertlib.assert_state_match(sriov_interface)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_increase_vfs(sriov_interface):
desired_state = sriov_interface
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 5
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_decrease_vfs(sriov_interface):
desired_state = sriov_interface
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 1
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = [VF0_CONF]
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_create_vf_config(sriov_iface_vf):
assertlib.assert_state_match(sriov_iface_vf)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_edit_vf_config(sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
vf0[Ethernet.SRIOV.VFS.TRUST] = True
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC3
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
@pytest.mark.xfail(
raises=libnmstate.error.NmstateVerificationError,
reason="https://github.com/nmstate/nmstate/issues/1454",
strict=True,
)
def test_sriov_remove_vf_config(sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = []
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_vf_mac_mixed_case(sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE
libnmstate.apply(desired_state)
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE.upper()
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_wait_sriov_vf_been_created():
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2}
},
}
]
}
try:
def test_sriov_increase_vfs(self, sriov_interface):
desired_state = sriov_interface
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 5
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only((f"{pf_name}v0", f"{pf_name}v1"))
assert len(current_state[Interface.KEY]) == 2
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
libnmstate.apply(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_wait_sriov_vf_been_deleted_when_total_vfs_decrease():
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2}
},
}
def test_sriov_decrease_vfs(self, sriov_interface):
desired_state = sriov_interface
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.TOTAL_VFS] = 1
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = [
VF0_CONF
]
}
try:
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only((f"{pf_name}v0", f"{pf_name}v1"))
assert len(current_state[Interface.KEY]) == 2
desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE][
Ethernet.SRIOV_SUBTREE
][Ethernet.SRIOV.TOTAL_VFS] = 1
def test_sriov_create_vf_config(self, sriov_iface_vf):
assertlib.assert_state_match(sriov_iface_vf)
def test_sriov_edit_vf_config(self, sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
vf0[Ethernet.SRIOV.VFS.TRUST] = True
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC3
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only((f"{pf_name}v0", f"{pf_name}v1"))
assert len(current_state[Interface.KEY]) == 1
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
@pytest.mark.xfail(
raises=libnmstate.error.NmstateVerificationError,
reason="https://github.com/nmstate/nmstate/issues/1454",
strict=True,
)
def test_sriov_remove_vf_config(self, sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE] = []
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
def test_sriov_vf_mac_mixed_case(self, sriov_iface_vf):
desired_state = sriov_iface_vf
eth_config = desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE]
vf0 = eth_config[Ethernet.SRIOV_SUBTREE][Ethernet.SRIOV.VFS_SUBTREE][0]
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE
libnmstate.apply(desired_state)
vf0[Ethernet.SRIOV.VFS.MAC_ADDRESS] = MAC_MIX_CASE.upper()
assertlib.assert_state_match(desired_state)
@pytest.mark.skipif(
not os.environ.get("TEST_REAL_NIC"),
reason="Need to define TEST_REAL_NIC for SR-IOV test",
)
def test_sriov_vf_vlan_id_and_qos():
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {
Ethernet.SRIOV.TOTAL_VFS: 2,
Ethernet.SRIOV.VFS_SUBTREE: [
{
Ethernet.SRIOV.VFS.ID: 0,
Ethernet.SRIOV.VFS.VLAN_ID: 100,
Ethernet.SRIOV.VFS.QOS: 5,
},
{
Ethernet.SRIOV.VFS.ID: 1,
Ethernet.SRIOV.VFS.VLAN_ID: 102,
Ethernet.SRIOV.VFS.QOS: 6,
},
],
}
def test_wait_sriov_vf_been_created(self):
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2}
},
}
]
}
try:
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only(
(f"{pf_name}v0", f"{pf_name}v1")
)
assert len(current_state[Interface.KEY]) == 2
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
libnmstate.apply(desired_state)
def test_wait_sriov_vf_been_deleted_when_total_vfs_decrease(self):
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {Ethernet.SRIOV.TOTAL_VFS: 2}
},
}
]
}
try:
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only(
(f"{pf_name}v0", f"{pf_name}v1")
)
assert len(current_state[Interface.KEY]) == 2
desired_state[Interface.KEY][0][Ethernet.CONFIG_SUBTREE][
Ethernet.SRIOV_SUBTREE
][Ethernet.SRIOV.TOTAL_VFS] = 1
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
current_state = statelib.show_only(
(f"{pf_name}v0", f"{pf_name}v1")
)
assert len(current_state[Interface.KEY]) == 1
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
libnmstate.apply(desired_state)
def test_sriov_vf_vlan_id_and_qos(self):
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: pf_name,
Ethernet.CONFIG_SUBTREE: {
Ethernet.SRIOV_SUBTREE: {
Ethernet.SRIOV.TOTAL_VFS: 2,
Ethernet.SRIOV.VFS_SUBTREE: [
{
Ethernet.SRIOV.VFS.ID: 0,
Ethernet.SRIOV.VFS.VLAN_ID: 100,
Ethernet.SRIOV.VFS.QOS: 5,
},
{
Ethernet.SRIOV.VFS.ID: 1,
Ethernet.SRIOV.VFS.VLAN_ID: 102,
Ethernet.SRIOV.VFS.QOS: 6,
},
],
}
},
}
]
}
try:
libnmstate.apply(desired_state)
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
libnmstate.apply(desired_state)
def test_refer_vf_using_pf_name_and_vf_id(self, sriov_interface):
pf_name = _test_nic_name()
desired_state = {
Interface.KEY: [
{
Interface.NAME: f"sriov:{pf_name}:0",
Interface.TYPE: InterfaceType.ETHERNET,
Interface.MTU: 1280,
},
}
]
}
try:
libnmstate.apply(desired_state)
finally:
desired_state[Interface.KEY][0][
Interface.STATE
] = InterfaceState.ABSENT
libnmstate.apply(desired_state)
{
Interface.NAME: f"sriov:{pf_name}:1",
Interface.TYPE: InterfaceType.ETHERNET,
Interface.MTU: 1281,
},
]
}
expected_state = copy.deepcopy(desired_state)
expected_state[Interface.KEY][0][Interface.NAME] = find_vf_iface_name(
pf_name, 0
)
expected_state[Interface.KEY][1][Interface.NAME] = find_vf_iface_name(
pf_name, 1
)
try:
libnmstate.apply(desired_state)
assertlib.assert_state_match(expected_state)
finally:
libnmstate.apply(
{
Interface.KEY: [
{
Interface.NAME: expected_state[Interface.KEY][0][
Interface.NAME
],
Interface.STATE: InterfaceState.ABSENT,
},
{
Interface.NAME: expected_state[Interface.KEY][0][
Interface.NAME
],
Interface.STATE: InterfaceState.ABSENT,
},
]
}
)
def test_refer_vf_using_pf_name_and_vf_id_bond(self, sriov_interface):
pf_name = _test_nic_name()
with bond_interface(
TEST_BOND, [f"sriov:{pf_name}:0", f"sriov:{pf_name}:1"]
) as expected_state:
expected_state[Interface.KEY][0][Bond.CONFIG_SUBTREE][
Bond.PORT
] = [
find_vf_iface_name(pf_name, 0),
find_vf_iface_name(pf_name, 1),
]
assertlib.assert_state_match(expected_state)
def test_refer_vf_using_pf_name_and_vf_id_linux_bridge(
self, sriov_interface
):
pf_name = _test_nic_name()
bridge_state = create_bridge_subtree_state()
add_port_to_bridge(bridge_state, f"sriov:{pf_name}:0")
add_port_to_bridge(bridge_state, f"sriov:{pf_name}:1")
# Disable STP to avoid topology changes and the consequence link change
options_subtree = bridge_state[LinuxBridge.OPTIONS_SUBTREE]
options_subtree[LinuxBridge.STP_SUBTREE][
LinuxBridge.STP.ENABLED
] = False
with linux_bridge(TEST_BRIDGE, bridge_state) as expected_state:
expected_state[Interface.KEY][0][LinuxBridge.CONFIG_SUBTREE][
LinuxBridge.PORT_SUBTREE
] = [
{
LinuxBridge.Port.NAME: port_name,
}
for port_name in (
find_vf_iface_name(pf_name, 0),
find_vf_iface_name(pf_name, 1),
)
]
assertlib.assert_state_match(expected_state)
def test_refer_vf_using_pf_name_and_vf_id_ovs_bridge(
self, sriov_interface
):
pf_name = _test_nic_name()
with ovs_bridge(
TEST_BRIDGE,
[f"sriov:{pf_name}:0", f"sriov:{pf_name}:1"],
TEST_OVS_IFACE,
) as expected_state:
expected_state[Interface.KEY][0][OVSBridge.CONFIG_SUBTREE][
OVSBridge.PORT_SUBTREE
] = [
{
OVSBridge.Port.NAME: port_name,
}
for port_name in (
find_vf_iface_name(pf_name, 0),
find_vf_iface_name(pf_name, 1),
TEST_OVS_IFACE,
)
]
assertlib.assert_state_match(expected_state)
def test_refer_vf_using_pf_name_and_vf_id_ovs_bond(self, sriov_interface):
pf_name = _test_nic_name()
with ovs_bridge_bond(
TEST_BRIDGE,
{TEST_BOND: [f"sriov:{pf_name}:0", f"sriov:{pf_name}:1"]},
TEST_OVS_IFACE,
) as expected_state:
ports = [
{OVSBridge.Port.LinkAggregation.Port.NAME: port_name}
for port_name in (
find_vf_iface_name(pf_name, 0),
find_vf_iface_name(pf_name, 1),
)
]
expected_state[Interface.KEY][0][OVSBridge.CONFIG_SUBTREE][
OVSBridge.PORT_SUBTREE
] = [
{
OVSBridge.Port.NAME: TEST_BOND,
OVSBridge.Port.LINK_AGGREGATION_SUBTREE: {
OVSBridge.Port.LinkAggregation.PORT_SUBTREE: ports,
},
},
{OVSBridge.Port.NAME: TEST_OVS_IFACE},
]
assertlib.assert_state_match(expected_state)

View File

@ -155,3 +155,25 @@ def _set_ifaces_state(ifaces, state):
for iface in ifaces:
iface[Interface.STATE] = state
return ifaces
@contextmanager
def ovs_bridge(br_name, sys_port_names, internal_port_name):
bridge = Bridge(br_name)
bridge.add_internal_port(internal_port_name)
for sys_port_name in sys_port_names:
bridge.add_system_port(sys_port_name)
with bridge.create():
yield bridge.state
@contextmanager
# The bond_ports should be in the format of
# dict(bond_port_name, bond_sys_port_names)
def ovs_bridge_bond(br_name, bond_ports, internal_port_name):
bridge = Bridge(br_name)
bridge.add_internal_port(internal_port_name)
for bond_port_name, bond_sys_port_names in bond_ports.items():
bridge.add_link_aggregation_port(bond_port_name, bond_sys_port_names)
with bridge.create():
yield bridge.state