nm.team: Team interface basic implementation
This is a team interface state example: interfaces: - name: team0 type: team state: up ipv4: enabled: false ipv6: enabled: false mtu: 1500 team: ports: - name: eth1 - name: eth2 runner: mode: loadbalance Signed-off-by: Fernando Fernandez Mancera <ffmancera@riseup.net>
This commit is contained in:
parent
224b99150c
commit
8585d91ebf
@ -283,7 +283,7 @@ if [[ "$CI" == "true" ]];then
|
||||
fi
|
||||
|
||||
mkdir -p $EXPORT_DIR
|
||||
CONTAINER_ID="$(${CONTAINER_CMD} run --privileged -d -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v $PROJECT_PATH:$CONTAINER_WORKSPACE -v $EXPORT_DIR:$CONT_EXPORT_DIR $CONTAINER_IMAGE)"
|
||||
CONTAINER_ID="$(${CONTAINER_CMD} run --privileged -d -e CI=$CI -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v $PROJECT_PATH:$CONTAINER_WORKSPACE -v $EXPORT_DIR:$CONT_EXPORT_DIR $CONTAINER_IMAGE)"
|
||||
[ -n "$debug_exit_shell" ] && trap open_shell EXIT || trap run_exit EXIT
|
||||
|
||||
if [[ -v copr_repo ]];then
|
||||
|
@ -103,6 +103,7 @@ def _interfaces():
|
||||
iface_info.update(nm.vlan.get_info(dev))
|
||||
iface_info.update(nm.vxlan.get_info(dev))
|
||||
iface_info.update(nm.bridge.get_info(dev))
|
||||
iface_info.update(nm.team.get_info(dev))
|
||||
|
||||
if nm.bond.is_bond_type_id(type_id):
|
||||
bondinfo = nm.bond.get_bond_info(dev)
|
||||
|
@ -400,7 +400,7 @@ def _build_connection_profile(iface_desired_state, base_con_profile=None):
|
||||
if sriov_setting:
|
||||
settings.append(sriov_setting)
|
||||
|
||||
team_setting = team.create_setting(iface_desired_state)
|
||||
team_setting = team.create_setting(iface_desired_state, base_con_profile)
|
||||
if team_setting:
|
||||
settings.append(team_setting)
|
||||
|
||||
|
@ -196,6 +196,11 @@ class ConnectionProfile:
|
||||
"Missing 'NetworkManager-ovs' plugin"
|
||||
f" to handle device={self.devname}"
|
||||
)
|
||||
elif self._is_team_plugin_missing(e):
|
||||
self._mainloop.quit(
|
||||
"Missing 'NetworkManager-team' plugin"
|
||||
f" to handle device={self.devname}"
|
||||
)
|
||||
else:
|
||||
self._mainloop.quit(
|
||||
"Connection activation failed on {} {}: error={}".format(
|
||||
@ -257,6 +262,15 @@ class ConnectionProfile:
|
||||
)
|
||||
)
|
||||
|
||||
def _is_team_plugin_missing(self, err):
|
||||
return (
|
||||
isinstance(err, nmclient.GLib.GError)
|
||||
and err.domain == nmclient.NM_MANAGER_ERROR_DOMAIN
|
||||
and self._con_profile.is_type(
|
||||
nmclient.NM.SETTING_TEAM_SETTING_NAME
|
||||
)
|
||||
)
|
||||
|
||||
def _get_activation_metadata(self):
|
||||
if self._nmdevice:
|
||||
activation_type = "device"
|
||||
|
@ -17,14 +17,99 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from libnmstate.error import NmstateNotImplementedError
|
||||
import json
|
||||
|
||||
from libnmstate.nm import connection as nm_connection
|
||||
from libnmstate.nm import nmclient
|
||||
from libnmstate.schema import Interface
|
||||
from libnmstate.schema import Team
|
||||
|
||||
|
||||
def create_setting(iface_state):
|
||||
setting = None
|
||||
team_config = iface_state.get(Team.CONFIG_SUBTREE)
|
||||
if team_config:
|
||||
raise NmstateNotImplementedError
|
||||
TEAMD_JSON_DEVICE = "device"
|
||||
|
||||
return setting
|
||||
|
||||
def create_setting(iface_state, base_con_profile):
|
||||
team_setting = None
|
||||
team_config = iface_state.get(Team.CONFIG_SUBTREE)
|
||||
|
||||
if not team_config:
|
||||
return None
|
||||
|
||||
if base_con_profile:
|
||||
team_setting = base_con_profile.get_setting_duplicate(
|
||||
nmclient.NM.SETTING_TEAM_SETTING_NAME
|
||||
)
|
||||
|
||||
if not team_setting:
|
||||
team_setting = nmclient.NM.SettingTeam.new()
|
||||
|
||||
teamd_config = _convert_team_config_to_teamd_format(
|
||||
team_config, iface_state[Interface.NAME]
|
||||
)
|
||||
|
||||
team_setting.props.config = json.dumps(teamd_config)
|
||||
|
||||
return team_setting
|
||||
|
||||
|
||||
def _convert_team_config_to_teamd_format(team_config, ifname):
|
||||
team_config[TEAMD_JSON_DEVICE] = ifname
|
||||
|
||||
team_ports = team_config.get(Team.PORT_SUBTREE, ())
|
||||
team_ports_formatted = {
|
||||
port[Team.Port.NAME]: _dict_key_filter(port, Team.Port.NAME)
|
||||
for port in team_ports
|
||||
}
|
||||
team_config[Team.PORT_SUBTREE] = team_ports_formatted
|
||||
|
||||
return team_config
|
||||
|
||||
|
||||
def _dict_key_filter(dict_to_filter, key):
|
||||
return dict(filter(lambda elem: elem[0] == key, dict_to_filter.items()))
|
||||
|
||||
|
||||
def get_info(device):
|
||||
"""
|
||||
Provide the current active teamd values for an interface.
|
||||
Ref: https://bugzilla.redhat.com/1792232
|
||||
"""
|
||||
info = {}
|
||||
|
||||
connection = nm_connection.ConnectionProfile()
|
||||
connection.import_by_device(device)
|
||||
if not connection.profile:
|
||||
return info
|
||||
|
||||
team_setting = connection.profile.get_setting_by_name(
|
||||
nmclient.NM.SETTING_TEAM_SETTING_NAME
|
||||
)
|
||||
|
||||
if team_setting:
|
||||
teamd_json = team_setting.get_config()
|
||||
if teamd_json:
|
||||
teamd_config = json.loads(teamd_json)
|
||||
team_config = _convert_teamd_config_to_nmstate_config(teamd_config)
|
||||
info[Team.CONFIG_SUBTREE] = team_config
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def _convert_teamd_config_to_nmstate_config(team_config):
|
||||
team_config.pop("device")
|
||||
port_config = team_config.get(Team.PORT_SUBTREE)
|
||||
|
||||
if port_config:
|
||||
team_port = _teamd_port_to_nmstate_port(port_config)
|
||||
team_config[Team.PORT_SUBTREE] = team_port
|
||||
|
||||
return team_config
|
||||
|
||||
|
||||
def _teamd_port_to_nmstate_port(port_config):
|
||||
port_list = []
|
||||
for name, port in port_config.items():
|
||||
port.update({Team.Port.NAME: name})
|
||||
port_list.append(port)
|
||||
|
||||
return port_list
|
||||
|
@ -44,6 +44,7 @@ class Api2Nm:
|
||||
"ethernet": nmclient.NM.SETTING_WIRED_SETTING_NAME,
|
||||
"bond": nmclient.NM.SETTING_BOND_SETTING_NAME,
|
||||
"dummy": nmclient.NM.SETTING_DUMMY_SETTING_NAME,
|
||||
"team": nmclient.NM.SETTING_TEAM_SETTING_NAME,
|
||||
"vlan": nmclient.NM.SETTING_VLAN_SETTING_NAME,
|
||||
"vxlan": nmclient.NM.SETTING_VXLAN_SETTING_NAME,
|
||||
"linux-bridge": nmclient.NM.SETTING_BRIDGE_SETTING_NAME,
|
||||
|
@ -17,6 +17,7 @@ RUN dnf copr enable nmstate/ovs-el8 -y && \
|
||||
RUN dnf -y install --setopt=install_weak_deps=False \
|
||||
NetworkManager \
|
||||
NetworkManager-ovs \
|
||||
NetworkManager-team \
|
||||
NetworkManager-config-server \
|
||||
openvswitch2.11 \
|
||||
systemd-udev \
|
||||
|
@ -11,6 +11,7 @@ RUN bash ./docker_enable_systemd.sh && rm ./docker_enable_systemd.sh
|
||||
RUN dnf -y install --setopt=install_weak_deps=False \
|
||||
NetworkManager \
|
||||
NetworkManager-ovs \
|
||||
NetworkManager-team \
|
||||
NetworkManager-config-server \
|
||||
dhcp-client \
|
||||
openvswitch \
|
||||
|
@ -29,8 +29,10 @@ Requires: NetworkManager-libnm >= 1:1.20
|
||||
Recommends: NetworkManager
|
||||
# Avoid automatically generated profiles
|
||||
Recommends: NetworkManager-config-server
|
||||
# Use Suggests for NetworkManager-ovs since it is only required for OVS support
|
||||
# Use Suggests for NetworkManager-ovs and NetworkManager-team since it is only
|
||||
# required for OVS and team support
|
||||
Suggests: NetworkManager-ovs
|
||||
Suggests: NetworkManager-team
|
||||
|
||||
|
||||
%description -n python3-%{libname}
|
||||
|
@ -25,6 +25,7 @@ import pytest
|
||||
|
||||
import libnmstate
|
||||
|
||||
from .testlib import cmdlib
|
||||
from .testlib import ifacelib
|
||||
|
||||
|
||||
@ -98,3 +99,10 @@ def _get_osname():
|
||||
if line.startswith("PRETTY_NAME="):
|
||||
return line.split("=", maxsplit=1)[1].strip().strip('"')
|
||||
return ""
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def download_nm_team_plugin():
|
||||
cmdlib.exec_cmd(
|
||||
("dnf", "install", "-y", "--downloadonly", "NetworkManager-team",)
|
||||
)
|
||||
|
@ -17,34 +17,115 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import libnmstate
|
||||
from libnmstate.error import NmstateNotImplementedError
|
||||
from libnmstate.error import NmstateLibnmError
|
||||
from libnmstate.schema import Interface
|
||||
from libnmstate.schema import InterfaceState
|
||||
from libnmstate.schema import InterfaceType
|
||||
from libnmstate.schema import Team
|
||||
|
||||
from .testlib import assertlib
|
||||
from .testlib import cmdlib
|
||||
|
||||
|
||||
DNF_INSTALL_NM_TEAM_PLUGIN_CMD = (
|
||||
"dnf",
|
||||
"install",
|
||||
"-y",
|
||||
"--cacheonly",
|
||||
"NetworkManager-team",
|
||||
)
|
||||
|
||||
DNF_REMOVE_NM_TEAM_PLUGIN_CMD = (
|
||||
"dnf",
|
||||
"remove",
|
||||
"-y",
|
||||
"-q",
|
||||
"NetworkManager-team",
|
||||
)
|
||||
|
||||
SYSTEMCTL_RESTART_NM_CMD = ("systemctl", "restart", "NetworkManager")
|
||||
|
||||
TEAM0 = "team0"
|
||||
|
||||
TEAM0_STATE = {
|
||||
Interface.KEY: [
|
||||
{
|
||||
Interface.NAME: TEAM0,
|
||||
Interface.TYPE: InterfaceType.TEAM,
|
||||
Team.CONFIG_SUBTREE: {
|
||||
Team.PORT_SUBTREE: [{Team.Port.NAME: "eth1"}]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
raises=NmstateNotImplementedError,
|
||||
reason="Team interface is not supported yet",
|
||||
strict=True,
|
||||
pytestmark = pytest.mark.skipif(
|
||||
os.environ.get("CI") == "true",
|
||||
reason="Team kmod not available in Travis CI",
|
||||
)
|
||||
def test_sriov_not_implemented():
|
||||
libnmstate.apply(TEAM0_STATE)
|
||||
|
||||
|
||||
def test_create_team_iface():
|
||||
with team_interface(TEAM0) as team_state:
|
||||
assertlib.assert_state(team_state)
|
||||
|
||||
|
||||
def test_edit_team_iface():
|
||||
with team_interface(TEAM0) as team_state:
|
||||
team_state[Interface.KEY][0][Team.CONFIG_SUBTREE] = {
|
||||
Team.RUNNER_SUBTREE: {
|
||||
Team.Runner.NAME: Team.Runner.RunnerMode.LOAD_BALANCE
|
||||
},
|
||||
Team.PORT_SUBTREE: [{Team.Port.NAME: "eth1"}],
|
||||
}
|
||||
libnmstate.apply(team_state)
|
||||
assertlib.assert_state(team_state)
|
||||
|
||||
|
||||
def test_nm_team_plugin_missing():
|
||||
with disable_nm_team_plugin():
|
||||
with pytest.raises(NmstateLibnmError):
|
||||
libnmstate.apply(
|
||||
{
|
||||
Interface.KEY: [
|
||||
{
|
||||
Interface.NAME: TEAM0,
|
||||
Interface.TYPE: InterfaceType.TEAM,
|
||||
Interface.STATE: InterfaceState.UP,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def team_interface(ifname):
|
||||
desired_state = {
|
||||
Interface.KEY: [
|
||||
{
|
||||
Interface.NAME: ifname,
|
||||
Interface.TYPE: InterfaceType.TEAM,
|
||||
Interface.STATE: InterfaceState.UP,
|
||||
}
|
||||
]
|
||||
}
|
||||
libnmstate.apply(desired_state)
|
||||
try:
|
||||
yield desired_state
|
||||
finally:
|
||||
libnmstate.apply(
|
||||
{
|
||||
Interface.KEY: [
|
||||
{
|
||||
Interface.NAME: ifname,
|
||||
Interface.STATE: InterfaceState.ABSENT,
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def disable_nm_team_plugin():
|
||||
cmdlib.exec_cmd(DNF_REMOVE_NM_TEAM_PLUGIN_CMD)
|
||||
cmdlib.exec_cmd(SYSTEMCTL_RESTART_NM_CMD)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
cmdlib.exec_cmd(DNF_INSTALL_NM_TEAM_PLUGIN_CMD)
|
||||
cmdlib.exec_cmd(SYSTEMCTL_RESTART_NM_CMD)
|
||||
|
@ -48,6 +48,7 @@ def test_api2nm_iface_type_map(NM_mock):
|
||||
"ovs-bridge": NM_mock.SETTING_OVS_BRIDGE_SETTING_NAME,
|
||||
"ovs-port": NM_mock.SETTING_OVS_PORT_SETTING_NAME,
|
||||
"ovs-interface": NM_mock.SETTING_OVS_INTERFACE_SETTING_NAME,
|
||||
"team": NM_mock.SETTING_TEAM_SETTING_NAME,
|
||||
"vlan": NM_mock.SETTING_VLAN_SETTING_NAME,
|
||||
"linux-bridge": NM_mock.SETTING_BRIDGE_SETTING_NAME,
|
||||
"vxlan": NM_mock.SETTING_VXLAN_SETTING_NAME,
|
||||
|
Loading…
x
Reference in New Issue
Block a user