Skip to content

Commit 7765603

Browse files
committed
feat: Add VLAN management for network_interface object
Add VLAN management for network_interface object in Linux OS. This will allow control over a VLAN interface inside Virtual Machine. Refs: #5 Signed-off-by: Kacper Tokarzewski <[email protected]>
1 parent 2059017 commit 7765603

File tree

2 files changed

+207
-0
lines changed
  • mfd_network_adapter/network_interface/feature/vlan
  • tests/unit/test_mfd_network_adapter/test_network_interface/test_feature/test_vlan

2 files changed

+207
-0
lines changed

mfd_network_adapter/network_interface/feature/vlan/linux.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import logging
66

77
from mfd_common_libs import add_logging_level, log_levels
8+
from mfd_common_libs.log_levels import MFD_DEBUG
9+
810
from .base import BaseFeatureVLAN
911

1012
logger = logging.getLogger(__name__)
@@ -13,3 +15,91 @@
1315

1416
class LinuxVLAN(BaseFeatureVLAN):
1517
"""Linux class for VLAN feature."""
18+
19+
def get_vlan_ids(self) -> list[int]:
20+
"""
21+
Get all VLAN IDs on Linux network interface via terminal command.
22+
23+
:return: List of VLAN IDs
24+
"""
25+
logger.log(
26+
level=MFD_DEBUG,
27+
msg=f"Getting VLAN IDs on interface {self._interface().name}.",
28+
)
29+
result = self.owner._connection.execute_command(
30+
f"ifconfig | grep -oP '{self._interface().name}.\\d+'",
31+
expected_return_codes={0},
32+
).stdout
33+
34+
vlan_ids = []
35+
if result:
36+
lines = result.strip().splitlines()
37+
for line in lines:
38+
if f"{self._interface().name}." in line:
39+
vlan_id_str = line.replace(f"{self._interface().name}.", "")
40+
if vlan_id_str.isdigit():
41+
vlan_id = int(vlan_id_str)
42+
if vlan_id not in vlan_ids: # Avoid duplicates
43+
vlan_ids.append(vlan_id)
44+
logger.log(
45+
level=MFD_DEBUG,
46+
msg=f"VLAN IDs on interface {self._interface().name}: {vlan_ids}.",
47+
)
48+
else:
49+
logger.log(level=MFD_DEBUG, msg=f"No VLANs set on {self._interface().name}.")
50+
51+
return vlan_ids
52+
53+
def get_vlan_id(self) -> int:
54+
"""
55+
Get first VLAN ID on Linux network interface via terminal command.
56+
57+
:return: First VLAN ID or 0 if none found
58+
"""
59+
vlan_ids = self.get_vlan_ids()
60+
return vlan_ids[0] if vlan_ids else 0
61+
62+
def add_vlan(self, vlan_id: int) -> bool:
63+
"""
64+
Add VLAN on Linux network interface via terminal command.
65+
66+
:param vlan_id: VLAN ID to create
67+
:return: True if VLAN was added successfully, False otherwise
68+
"""
69+
logger.log(
70+
level=MFD_DEBUG,
71+
msg=f"Adding VLAN {vlan_id} on interface {self._interface().name}.",
72+
)
73+
command = (
74+
f"ip link add link {self._interface().name} name {self._interface().name}.{vlan_id} "
75+
f"type vlan id {vlan_id}"
76+
)
77+
78+
self.owner._connection.execute_command(command, expected_return_codes={0})
79+
80+
return vlan_id in self.get_vlan_ids()
81+
82+
def remove_vlan(self, vlan_id: int = 0) -> bool:
83+
"""
84+
Remove VLAN on Linux network interface via terminal command.
85+
86+
:param vlan_id: VLAN ID to remove, if 0 then first collected VLAN ID will be used
87+
:return: True if VLAN was removed successfully, False otherwise
88+
"""
89+
if vlan_id == 0:
90+
vlan_id = self.get_vlan_id()
91+
if vlan_id == 0:
92+
logger.log(
93+
level=MFD_DEBUG,
94+
msg=f"No VLANs found on interface {self._interface().name}.",
95+
)
96+
return False
97+
logger.log(level=MFD_DEBUG, msg=f"Removing VLAN on interface {self._interface().name}.")
98+
command = (
99+
f"ip link del link {self._interface().name} name {self._interface().name}.{vlan_id} "
100+
f"type vlan id {vlan_id}"
101+
)
102+
103+
self.owner._connection.execute_command(command, expected_return_codes={0})
104+
105+
return vlan_id not in self.get_vlan_ids()
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Copyright (C) 2025 Intel Corporation
2+
# SPDX-License-Identifier: MIT
3+
import pytest
4+
from mfd_connect import RPyCConnection
5+
from mfd_connect.base import ConnectionCompletedProcess
6+
from mfd_typing import PCIAddress, OSName
7+
from mfd_typing.network_interface import LinuxInterfaceInfo
8+
9+
from mfd_network_adapter.network_adapter_owner.linux import LinuxNetworkAdapterOwner
10+
from mfd_network_adapter.network_interface.feature.vlan import LinuxVLAN
11+
from mfd_network_adapter.network_interface.linux import LinuxNetworkInterface
12+
13+
14+
class TestVlanLinux:
15+
@pytest.fixture()
16+
def vlan(self, mocker):
17+
pci_address = PCIAddress(0, 0, 0, 0)
18+
name = "eth1"
19+
mock_connection = mocker.create_autospec(RPyCConnection)
20+
mock_connection.get_os_name.return_value = OSName.LINUX
21+
22+
interface = LinuxNetworkInterface(
23+
connection=mock_connection,
24+
interface_info=LinuxInterfaceInfo(pci_address=pci_address, name=name),
25+
)
26+
27+
mock_owner = mocker.create_autospec(LinuxNetworkAdapterOwner)
28+
mock_owner._connection = mock_connection
29+
30+
vlan = LinuxVLAN(connection=mock_connection, interface=interface)
31+
vlan.owner = mock_owner
32+
vlan._interface = lambda: interface
33+
34+
return vlan
35+
36+
def test_get_vlan_id_vlan_exist(self, vlan):
37+
vlan.owner._connection.execute_command.return_value = ConnectionCompletedProcess(
38+
return_code=0, args="command", stdout="eth1.100\n", stderr="stderr"
39+
)
40+
result = vlan.get_vlan_id()
41+
vlan.owner._connection.execute_command.assert_called_once_with(
42+
"ifconfig | grep -oP 'eth1.\\d+'",
43+
expected_return_codes={0},
44+
)
45+
assert result == 100
46+
47+
def test_get_vlan_id_vlan_does_not_exist(self, vlan):
48+
vlan.owner._connection.execute_command.return_value = ConnectionCompletedProcess(
49+
return_code=0, args="command", stdout="", stderr="stderr"
50+
)
51+
result = vlan.get_vlan_id()
52+
vlan.owner._connection.execute_command.assert_called_once_with(
53+
"ifconfig | grep -oP 'eth1.\\d+'",
54+
expected_return_codes={0},
55+
)
56+
assert result == 0
57+
58+
def test_add_vlan_positive(self, vlan, mocker):
59+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[100])
60+
result = vlan.add_vlan(100)
61+
vlan.owner._connection.execute_command.assert_called_once_with(
62+
"ip link add link eth1 name eth1.100 type vlan id 100",
63+
expected_return_codes={0},
64+
)
65+
assert result
66+
67+
def test_add_vlan_negative(self, vlan, mocker):
68+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[])
69+
result = vlan.add_vlan(100)
70+
vlan.owner._connection.execute_command.assert_called_once_with(
71+
"ip link add link eth1 name eth1.100 type vlan id 100",
72+
expected_return_codes={0},
73+
)
74+
assert not result
75+
76+
def test_remove_vlan_positive(self, vlan, mocker):
77+
mocker.patch.object(vlan, "get_vlan_id", return_value=100)
78+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[])
79+
result = vlan.remove_vlan()
80+
vlan.owner._connection.execute_command.assert_called_once_with(
81+
"ip link del link eth1 name eth1.100 type vlan id 100",
82+
expected_return_codes={0},
83+
)
84+
assert result
85+
86+
def test_remove_vlan_negative(self, vlan, mocker):
87+
mocker.patch.object(vlan, "get_vlan_id", return_value=100)
88+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[100])
89+
result = vlan.remove_vlan()
90+
vlan.owner._connection.execute_command.assert_called_once_with(
91+
"ip link del link eth1 name eth1.100 type vlan id 100",
92+
expected_return_codes={0},
93+
)
94+
assert not result
95+
96+
def test_remove_vlan_negative_no_vlan(self, vlan, mocker):
97+
mocker.patch.object(vlan, "get_vlan_id", return_value=0)
98+
result = vlan.remove_vlan()
99+
assert not result
100+
101+
def test_remove_specified_vlan_positive(self, vlan, mocker):
102+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[100])
103+
result = vlan.remove_vlan(vlan_id=101)
104+
vlan.owner._connection.execute_command.assert_called_once_with(
105+
"ip link del link eth1 name eth1.101 type vlan id 101",
106+
expected_return_codes={0},
107+
)
108+
assert result
109+
110+
def test_remove_specified_vlan_negative(self, vlan, mocker):
111+
mocker.patch.object(vlan, "get_vlan_ids", return_value=[100, 101])
112+
result = vlan.remove_vlan(vlan_id=101)
113+
vlan.owner._connection.execute_command.assert_called_once_with(
114+
"ip link del link eth1 name eth1.101 type vlan id 101",
115+
expected_return_codes={0},
116+
)
117+
assert not result

0 commit comments

Comments
 (0)