nm ovs: Support modification of OVS external_ids
NetworkManager 1.30+ has introduced the support of changing OVS `external_ids`. Integration test cases included. Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
parent
57e2547572
commit
c99e708cd8
@ -27,6 +27,7 @@ from libnmstate.schema import InterfaceType
|
||||
from libnmstate.schema import LinuxBridge as LB
|
||||
from libnmstate.schema import OVSBridge as OvsB
|
||||
from libnmstate.schema import OVSInterface
|
||||
from libnmstate.schema import OvsDB
|
||||
|
||||
from libnmstate.ifaces.bridge import BridgeIface
|
||||
|
||||
@ -42,6 +43,7 @@ from .lldp import apply_lldp_setting
|
||||
from .macvlan import create_setting as create_macvlan_setting
|
||||
from .ovs import create_bridge_setting as create_ovs_bridge_setting
|
||||
from .ovs import create_interface_setting as create_ovs_interface_setting
|
||||
from .ovs import create_ovsdb_external_ids_setting
|
||||
from .ovs import create_port_setting as create_ovs_port_setting
|
||||
from .sriov import create_setting as create_sriov_setting
|
||||
from .team import create_setting as create_team_setting
|
||||
@ -151,7 +153,7 @@ def create_new_nm_simple_conn(iface, nm_profile):
|
||||
settings.append(linux_bridge_setting)
|
||||
elif iface.type == InterfaceType.OVS_BRIDGE:
|
||||
ovs_bridge_state = iface_info.get(OvsB.CONFIG_SUBTREE, {})
|
||||
ovs_bridge_options = ovs_bridge_state.get(OvsB.OPTIONS_SUBTREE)
|
||||
ovs_bridge_options = ovs_bridge_state.get(OvsB.OPTIONS_SUBTREE, {})
|
||||
if ovs_bridge_options:
|
||||
settings.append(create_ovs_bridge_setting(ovs_bridge_options))
|
||||
elif iface.type == InterfaceType.OVS_PORT:
|
||||
@ -208,6 +210,20 @@ def create_new_nm_simple_conn(iface, nm_profile):
|
||||
if veth_setting:
|
||||
settings.append(veth_setting)
|
||||
|
||||
if (
|
||||
iface.controller_type
|
||||
in (
|
||||
InterfaceType.OVS_BRIDGE,
|
||||
InterfaceType.OVS_PORT,
|
||||
)
|
||||
or iface.type == InterfaceType.OVS_BRIDGE
|
||||
):
|
||||
nm_setting = create_ovsdb_external_ids_setting(
|
||||
iface_info.get(OvsDB.OVS_DB_SUBTREE, {})
|
||||
)
|
||||
if nm_setting:
|
||||
settings.append(nm_setting)
|
||||
|
||||
nm_simple_conn = NM.SimpleConnection.new()
|
||||
for setting in settings:
|
||||
nm_simple_conn.add_setting(setting)
|
||||
|
@ -20,13 +20,14 @@
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
|
||||
from libnmstate.ifaces import ovs
|
||||
from libnmstate.ifaces.bridge import BridgeIface
|
||||
from libnmstate.ifaces.ovs import OvsPortIface
|
||||
from libnmstate.schema import Interface
|
||||
from libnmstate.schema import InterfaceType
|
||||
from libnmstate.schema import OVSBridge as OB
|
||||
from libnmstate.schema import OVSInterface
|
||||
from libnmstate.ifaces import ovs
|
||||
from libnmstate.ifaces.bridge import BridgeIface
|
||||
from libnmstate.ifaces.ovs import OvsPortIface
|
||||
from libnmstate.schema import OvsDB
|
||||
|
||||
from .common import NM
|
||||
|
||||
@ -35,6 +36,8 @@ PORT_PROFILE_PREFIX = "ovs-port-"
|
||||
|
||||
CONTROLLER_TYPE_METADATA = "_controller_type"
|
||||
CONTROLLER_METADATA = "_controller"
|
||||
SETTING_OVS_EXTERNALIDS = "SettingOvsExternalIDs"
|
||||
SETTING_OVS_EXTERNAL_IDS_SETTING_NAME = "ovs-external-ids"
|
||||
|
||||
NM_OVS_VLAN_MODE_MAP = {
|
||||
"trunk": OB.Port.Vlan.Mode.TRUNK,
|
||||
@ -70,6 +73,21 @@ def create_bridge_setting(options_state):
|
||||
return bridge_setting
|
||||
|
||||
|
||||
def create_ovsdb_external_ids_setting(ovsdb_conf):
|
||||
if _is_nm_support_ovs_external_ids():
|
||||
nm_setting = getattr(NM, SETTING_OVS_EXTERNALIDS).new()
|
||||
for key, value in ovsdb_conf.get(OvsDB.EXTERNAL_IDS, {}).items():
|
||||
if not key.startswith("NM."):
|
||||
nm_setting.set_data(key, str(value))
|
||||
return nm_setting
|
||||
else:
|
||||
logging.warn(
|
||||
"Please upgrade NetworkManger to 1.30+ "
|
||||
"for the support OVS external ID modification"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def create_port_setting(port_state):
|
||||
port_setting = NM.SettingOvsPort.new()
|
||||
|
||||
@ -138,6 +156,22 @@ def get_ovs_bridge_info(nm_dev_ovs_br):
|
||||
return iface_info
|
||||
|
||||
|
||||
def get_ovsdb_external_ids(nm_profile):
|
||||
iface_info = {}
|
||||
if _is_nm_support_ovs_external_ids():
|
||||
nm_setting = nm_profile.get_setting_by_name(
|
||||
SETTING_OVS_EXTERNAL_IDS_SETTING_NAME
|
||||
)
|
||||
if nm_setting:
|
||||
external_ids = {}
|
||||
for key in nm_setting.get_data_keys():
|
||||
external_ids[key] = nm_setting.get_data(key)
|
||||
iface_info[OvsDB.OVS_DB_SUBTREE] = {
|
||||
OvsDB.EXTERNAL_IDS: external_ids
|
||||
}
|
||||
return iface_info
|
||||
|
||||
|
||||
def _get_bridge_nmstate_ports_info(nm_dev_ovs_br):
|
||||
ports_info = []
|
||||
for nm_dev_ovs_port in nm_dev_ovs_br.get_slaves():
|
||||
@ -288,3 +322,7 @@ def create_iface_for_nm_ovs_port(iface):
|
||||
CONTROLLER_TYPE_METADATA: iface_info[CONTROLLER_TYPE_METADATA],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _is_nm_support_ovs_external_ids():
|
||||
return hasattr(NM, SETTING_OVS_EXTERNALIDS)
|
||||
|
@ -46,6 +46,7 @@ from .lldp import get_info as get_lldp_info
|
||||
from .macvlan import get_current_macvlan_type
|
||||
from .ovs import get_interface_info as get_ovs_interface_info
|
||||
from .ovs import get_ovs_bridge_info
|
||||
from .ovs import get_ovsdb_external_ids
|
||||
from .ovs import has_ovs_capability
|
||||
from .profiles import NmProfiles
|
||||
from .profiles import get_all_applied_configs
|
||||
@ -151,6 +152,9 @@ class NetworkManagerPlugin(NmstatePlugin):
|
||||
elif iface_info[Interface.TYPE] == InterfaceType.OVS_PORT:
|
||||
continue
|
||||
|
||||
if applied_config:
|
||||
iface_info.update(get_ovsdb_external_ids(applied_config))
|
||||
|
||||
info.append(iface_info)
|
||||
|
||||
info.sort(key=itemgetter("name"))
|
||||
|
@ -24,16 +24,23 @@ from libnmstate.schema import Interface
|
||||
from libnmstate.schema import InterfaceIPv4
|
||||
from libnmstate.schema import InterfaceState
|
||||
from libnmstate.schema import InterfaceType
|
||||
from libnmstate.schema import OvsDB
|
||||
from libnmstate.schema import VLAN
|
||||
|
||||
from ..testlib import assertlib
|
||||
from ..testlib import statelib
|
||||
from ..testlib import cmdlib
|
||||
from ..testlib import statelib
|
||||
from ..testlib.env import nm_major_minor_version
|
||||
from ..testlib.ovslib import Bridge
|
||||
from ..testlib.plugin import tmp_plugin_dir
|
||||
|
||||
|
||||
BRIDGE0 = "brtest0"
|
||||
IFACE0 = "ovstest0"
|
||||
OVSDB_EXT_IDS_CONF1 = {"foo": "abc", "bak": 1}
|
||||
OVSDB_EXT_IDS_CONF1_STR = {"foo": "abc", "bak": "1"}
|
||||
OVSDB_EXT_IDS_CONF2 = {"bak": 2}
|
||||
OVSDB_EXT_IDS_CONF2_STR = {"bak": "2"}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -157,3 +164,99 @@ def _get_ovs_port_profile_uuid_of_ovs_interface(iface_name):
|
||||
check=True,
|
||||
)
|
||||
return ovs_port_uuid
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def disable_ovsdb_plugin():
|
||||
with tmp_plugin_dir():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
nm_major_minor_version() <= 1.28,
|
||||
reason="OVS external ID is not supported by NetworkManager 1.28-.",
|
||||
)
|
||||
class TestNmOvsExternalIds:
|
||||
def test_create_ovs_bridge_with_external_ids(self, disable_ovsdb_plugin):
|
||||
bridge = Bridge(BRIDGE0)
|
||||
bridge.set_ovs_db({OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF1})
|
||||
bridge.add_internal_port(
|
||||
IFACE0,
|
||||
ipv4_state={InterfaceIPv4.ENABLED: False},
|
||||
ovs_db={OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF2},
|
||||
)
|
||||
with bridge.create() as state:
|
||||
assertlib.assert_state_match(state)
|
||||
new_state = statelib.show_only((BRIDGE0, IFACE0))
|
||||
assert (
|
||||
new_state[Interface.KEY][0][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
== OVSDB_EXT_IDS_CONF1_STR
|
||||
)
|
||||
assert (
|
||||
new_state[Interface.KEY][1][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
== OVSDB_EXT_IDS_CONF2_STR
|
||||
)
|
||||
|
||||
def test_modify_ovs_bridge_external_ids(self, disable_ovsdb_plugin):
|
||||
bridge = Bridge(BRIDGE0)
|
||||
bridge.set_ovs_db({OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF1})
|
||||
bridge.add_internal_port(
|
||||
IFACE0,
|
||||
ipv4_state={InterfaceIPv4.ENABLED: False},
|
||||
ovs_db={OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF2},
|
||||
)
|
||||
with bridge.create():
|
||||
changed_bridge = Bridge(BRIDGE0)
|
||||
changed_bridge.set_ovs_db(
|
||||
{OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF2}
|
||||
)
|
||||
changed_bridge.add_internal_port(
|
||||
IFACE0,
|
||||
ipv4_state={InterfaceIPv4.ENABLED: False},
|
||||
ovs_db={OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF1},
|
||||
)
|
||||
changed_bridge.apply()
|
||||
assertlib.assert_state_match(changed_bridge.state)
|
||||
new_state = statelib.show_only((BRIDGE0, IFACE0))
|
||||
assert (
|
||||
new_state[Interface.KEY][0][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
== OVSDB_EXT_IDS_CONF2_STR
|
||||
)
|
||||
assert (
|
||||
new_state[Interface.KEY][1][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
== OVSDB_EXT_IDS_CONF1_STR
|
||||
)
|
||||
|
||||
def test_ovs_bridge_remoev_external_ids(self, disable_ovsdb_plugin):
|
||||
bridge = Bridge(BRIDGE0)
|
||||
bridge.set_ovs_db({OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF1})
|
||||
bridge.add_internal_port(
|
||||
IFACE0,
|
||||
ipv4_state={InterfaceIPv4.ENABLED: False},
|
||||
ovs_db={OvsDB.EXTERNAL_IDS: OVSDB_EXT_IDS_CONF2},
|
||||
)
|
||||
with bridge.create():
|
||||
changed_bridge = Bridge(BRIDGE0)
|
||||
changed_bridge.set_ovs_db({OvsDB.EXTERNAL_IDS: {}})
|
||||
changed_bridge.add_internal_port(
|
||||
IFACE0,
|
||||
ipv4_state={InterfaceIPv4.ENABLED: False},
|
||||
ovs_db={OvsDB.EXTERNAL_IDS: {}},
|
||||
)
|
||||
changed_bridge.apply()
|
||||
assertlib.assert_state_match(changed_bridge.state)
|
||||
new_state = statelib.show_only((BRIDGE0, IFACE0))
|
||||
assert not new_state[Interface.KEY][0][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
assert not new_state[Interface.KEY][1][OvsDB.OVS_DB_SUBTREE][
|
||||
OvsDB.EXTERNAL_IDS
|
||||
]
|
||||
|
@ -18,8 +18,6 @@
|
||||
#
|
||||
|
||||
import copy
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
@ -37,6 +35,7 @@ from libnmstate.plugin import NmstatePlugin
|
||||
|
||||
from .testlib import statelib
|
||||
from .testlib.servicelib import disable_service
|
||||
from .testlib.plugin import tmp_plugin_dir
|
||||
|
||||
|
||||
FOO_IFACE_NAME = "foo1"
|
||||
@ -151,37 +150,39 @@ LO_IFACE_INFO = {
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_plugin_dir():
|
||||
with tempfile.TemporaryDirectory() as plugin_dir:
|
||||
os.environ["NMSTATE_PLUGIN_DIR"] = plugin_dir
|
||||
yield plugin_dir
|
||||
os.environ.pop("NMSTATE_PLUGIN_DIR")
|
||||
def with_foo_plugin():
|
||||
with tmp_plugin_dir() as plugin_dir:
|
||||
_gen_plugin_foo(plugin_dir)
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_foo_plugin(tmp_plugin_dir):
|
||||
_gen_plugin_foo(tmp_plugin_dir)
|
||||
def with_multiple_plugins():
|
||||
with tmp_plugin_dir() as plugin_dir:
|
||||
_gen_plugin_foo(plugin_dir)
|
||||
_gen_plugin_bar(plugin_dir)
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_multiple_plugins(tmp_plugin_dir):
|
||||
_gen_plugin_foo(tmp_plugin_dir)
|
||||
_gen_plugin_bar(tmp_plugin_dir)
|
||||
def with_route_plugin():
|
||||
with tmp_plugin_dir() as plugin_dir:
|
||||
_gen_plugin_route_foo(plugin_dir)
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_route_plugin(tmp_plugin_dir):
|
||||
_gen_plugin_route_foo(tmp_plugin_dir)
|
||||
def with_route_rule_plugin():
|
||||
with tmp_plugin_dir() as plugin_dir:
|
||||
_gen_plugin_route_rule_foo(plugin_dir)
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_route_rule_plugin(tmp_plugin_dir):
|
||||
_gen_plugin_route_rule_foo(tmp_plugin_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_dns_plugin(tmp_plugin_dir):
|
||||
_gen_plugin_dns_foo(tmp_plugin_dir)
|
||||
def with_dns_plugin():
|
||||
with tmp_plugin_dir() as plugin_dir:
|
||||
_gen_plugin_dns_foo(plugin_dir)
|
||||
yield
|
||||
|
||||
|
||||
def _gen_plugin(
|
||||
|
32
tests/integration/testlib/plugin.py
Normal file
32
tests/integration/testlib/plugin.py
Normal file
@ -0,0 +1,32 @@
|
||||
#
|
||||
# Copyright (c) 2021 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/>.
|
||||
#
|
||||
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tmp_plugin_dir():
|
||||
with tempfile.TemporaryDirectory() as plugin_dir:
|
||||
os.environ["NMSTATE_PLUGIN_DIR"] = plugin_dir
|
||||
try:
|
||||
yield plugin_dir
|
||||
finally:
|
||||
os.environ.pop("NMSTATE_PLUGIN_DIR")
|
Loading…
x
Reference in New Issue
Block a user