ovs: Support interface level other_config (api break)

The Rust API changed:

    The `OvsBridgeBondConfig.other_config: Option<HashMap>` changed to
    `OvsBridgeBondConfig.ovs_db:  Option<OvsDbIfaceConfig>`

The YAML API changed:

    The `other_config` under ovs bond should be stored under `ovs-db`
    section, please check follow up example for detail.

Considering the interface level `other_config` never works in previous
release, this is API change is acceptable.

Example on `other_config` of OVS bridge:

```yml
interfaces:
- name: br0
  type: ovs-bridge
  state: up
  ovs-db:
    other_config:
      in-band-queue: '12'
  bridge:
    port:
    - name: eth1
    - name: ovs0
```

Example on `other_config` of OVS Bond:

```yml
- name: br0
  type: ovs-bridge
  state: up
  bridge:
    port:
    - name: bond1
      link-aggregation:
        mode: balance-slb
        ovs-db:
          other_config:
            bond-miimon-interval: "100"
        port:
          - name: eth2
          - name: eth1
```

Example on `other_config` of OVS interface:

```yml
- name: eth1
  type: ethernet
  state: up
  ovs-db:
    other_config:
      emc-insert-inv-prob: 90
```

We hide interface level `ovs-db` section if it is empty. This help us
supporting older NetworkManager where `other_config` is not supported
yet. User will get error when they apply above yaml on old
NetworkManager:

    NmstateError: DependencyError: Please upgrade NetworkManager for
    specified interface type: Connection(InvalidSetting):ovs-other-config:
    unknown setting name

Integration test case included.

Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
Gris Ge 2023-01-17 21:40:13 +08:00 committed by Fernando Fernández Mancera
parent e2b3ea72b9
commit 8ec213b8cb
16 changed files with 378 additions and 41 deletions

View File

@ -661,6 +661,9 @@ impl Interface {
if let Interface::LinuxBridge(iface) = self {
iface.sanitize_for_verify()
}
if let Interface::OvsBridge(iface) = self {
iface.sanitize_for_verify()
}
}
pub(crate) fn parent(&self) -> Option<&str> {

View File

@ -260,6 +260,10 @@ impl BaseInterface {
if let Some(ipv6_conf) = self.ipv6.as_mut() {
ipv6_conf.sanitize_for_verify();
}
// ovsdb None equal to empty
if self.ovsdb.is_none() {
self.ovsdb = Some(OvsDbIfaceConfig::empty());
}
}
}

View File

@ -1,13 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use crate::{
BaseInterface, BridgePortVlanConfig, ErrorKind, Interface, InterfaceType,
MergedInterface, NmstateError,
MergedInterface, NmstateError, OvsDbIfaceConfig,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -138,6 +137,20 @@ impl OvsBridgeInterface {
Ok(())
}
pub(crate) fn sanitize_for_verify(&mut self) {
if let Some(port_confs) = self
.bridge
.as_mut()
.and_then(|br_conf| br_conf.ports.as_mut())
{
for port_conf in port_confs {
if let Some(bond_conf) = port_conf.bond.as_mut() {
bond_conf.sanitize_for_verify();
}
}
}
}
// Only support remove non-bonding port or the bond itself as bond require
// two ports, removal any of them will trigger error.
pub(crate) fn remove_port(&mut self, port_name: &str) {
@ -499,11 +512,13 @@ pub struct OvsBridgeBondConfig {
)]
/// Deserialize and serialize from/to `bond-updelay`.
pub bond_updelay: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(skip_serializing_if = "Option::is_none", rename = "ovs-db")]
/// OpenvSwitch specific `other_config` for OVS bond. Please refer to
/// manpage `ovs-vswitchd.conf.db(5)` for more detail.
/// Set to None for remove specific entry.
pub other_config: Option<HashMap<String, Option<String>>>,
/// When setting to None, nmstate will try to preserve current
/// `other_config`, otherwise, nmstate will override all `other_config`
/// for specified OVS bond.
pub ovsdb: Option<OvsDbIfaceConfig>,
}
impl OvsBridgeBondConfig {
@ -526,6 +541,13 @@ impl OvsBridgeBondConfig {
bond_ports.sort_unstable_by_key(|p| p.name.clone())
}
}
pub(crate) fn sanitize_for_verify(&mut self) {
// None ovsbd equal to empty
if self.ovsdb.is_none() {
self.ovsdb = Some(OvsDbIfaceConfig::empty());
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]

View File

@ -19,7 +19,8 @@ use super::super::{
connection::mac_vlan::NmSettingMacVlan,
connection::ovs::{
NmSettingOvsBridge, NmSettingOvsDpdk, NmSettingOvsExtIds,
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort,
NmSettingOvsIface, NmSettingOvsOtherConfig, NmSettingOvsPatch,
NmSettingOvsPort,
},
connection::sriov::NmSettingSriov,
connection::user::NmSettingUser,
@ -86,6 +87,7 @@ pub struct NmConnection {
pub ovs_port: Option<NmSettingOvsPort>,
pub ovs_iface: Option<NmSettingOvsIface>,
pub ovs_ext_ids: Option<NmSettingOvsExtIds>,
pub ovs_other_config: Option<NmSettingOvsOtherConfig>,
pub ovs_patch: Option<NmSettingOvsPatch>,
pub ovs_dpdk: Option<NmSettingOvsDpdk>,
pub wired: Option<NmSettingWired>,
@ -151,6 +153,11 @@ impl TryFrom<NmConnectionDbusOwnedValue> for NmConnection {
"ovs-external-ids",
NmSettingOvsExtIds::try_from
)?,
ovs_other_config: _from_map!(
v,
"ovs-other-config",
NmSettingOvsOtherConfig::try_from
)?,
ovs_patch: _from_map!(v, "ovs-patch", NmSettingOvsPatch::try_from)?,
ovs_dpdk: _from_map!(v, "ovs-dpdk", NmSettingOvsDpdk::try_from)?,
wired: _from_map!(v, "802-3-ethernet", NmSettingWired::try_from)?,
@ -229,6 +236,9 @@ impl NmConnection {
if let Some(ovs_ext_ids) = &self.ovs_ext_ids {
ret.insert("ovs-external-ids", ovs_ext_ids.to_value()?);
}
if let Some(ovs_other_config) = &self.ovs_other_config {
ret.insert("ovs-other-config", ovs_other_config.to_value()?);
}
if let Some(ovs_patch_set) = &self.ovs_patch {
ret.insert("ovs-patch", ovs_patch_set.to_value()?);
}

View File

@ -52,7 +52,8 @@ pub use self::loopback::NmSettingLoopback;
pub use self::mac_vlan::NmSettingMacVlan;
pub use self::ovs::{
NmSettingOvsBridge, NmSettingOvsDpdk, NmSettingOvsExtIds,
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort,
NmSettingOvsIface, NmSettingOvsOtherConfig, NmSettingOvsPatch,
NmSettingOvsPort,
};
pub use self::route::NmIpRoute;
pub use self::route_rule::{NmIpRouteRule, NmIpRouteRuleAction};

View File

@ -210,6 +210,45 @@ impl ToDbusValue for NmSettingOvsExtIds {
}
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
#[serde(try_from = "DbusDictionary")]
#[non_exhaustive]
pub struct NmSettingOvsOtherConfig {
pub data: Option<HashMap<String, String>>,
_other: HashMap<String, zvariant::OwnedValue>,
}
impl TryFrom<DbusDictionary> for NmSettingOvsOtherConfig {
type Error = NmError;
fn try_from(mut v: DbusDictionary) -> Result<Self, Self::Error> {
Ok(Self {
data: _from_map!(v, "data", <HashMap<String, String>>::try_from)?,
_other: v,
})
}
}
impl ToDbusValue for NmSettingOvsOtherConfig {
fn to_value(&self) -> Result<HashMap<&str, zvariant::Value>, NmError> {
let mut ret = HashMap::new();
if let Some(v) = &self.data {
let mut dict_value = zvariant::Dict::new(
zvariant::Signature::from_str_unchecked("s"),
zvariant::Signature::from_str_unchecked("s"),
);
for (k, v) in v.iter() {
dict_value
.append(zvariant::Value::new(k), zvariant::Value::new(v))?;
}
ret.insert("data", zvariant::Value::Dict(dict_value));
}
ret.extend(self._other.iter().map(|(key, value)| {
(key.as_str(), zvariant::Value::from(value.clone()))
}));
Ok(ret)
}
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
#[serde(try_from = "DbusDictionary")]
#[non_exhaustive]

View File

@ -82,6 +82,9 @@ impl NmConnection {
if let Some(ovs_eids) = &self.ovs_ext_ids {
sections.push(("ovs-external-ids", ovs_eids.to_keyfile()?));
}
if let Some(ovs_other_cfgs) = &self.ovs_other_config {
sections.push(("ovs-other-config", ovs_other_cfgs.to_keyfile()?));
}
keyfile_sections_to_string(&sections)
}

View File

@ -4,7 +4,8 @@ use std::collections::HashMap;
use super::super::{
NmError, NmSettingOvsBridge, NmSettingOvsDpdk, NmSettingOvsExtIds,
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort, ToKeyfile,
NmSettingOvsIface, NmSettingOvsOtherConfig, NmSettingOvsPatch,
NmSettingOvsPort, ToKeyfile,
};
impl ToKeyfile for NmSettingOvsBridge {}
@ -24,3 +25,15 @@ impl ToKeyfile for NmSettingOvsExtIds {
Ok(ret)
}
}
impl ToKeyfile for NmSettingOvsOtherConfig {
fn to_keyfile(&self) -> Result<HashMap<String, zvariant::Value>, NmError> {
let mut ret = HashMap::new();
if let Some(data) = self.data.as_ref() {
for (k, v) in data {
ret.insert(format!("data.{k}"), zvariant::Value::new(v));
}
}
Ok(ret)
}
}

View File

@ -29,10 +29,10 @@ pub use self::connection::{
NmSettingBridgeVlanRange, NmSettingConnection, NmSettingEthtool,
NmSettingInfiniBand, NmSettingIp, NmSettingIpMethod, NmSettingLoopback,
NmSettingMacVlan, NmSettingOvsBridge, NmSettingOvsDpdk, NmSettingOvsExtIds,
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort, NmSettingSriov,
NmSettingSriovVf, NmSettingSriovVfVlan, NmSettingUser, NmSettingVeth,
NmSettingVlan, NmSettingVrf, NmSettingVxlan, NmSettingWired,
NmSettingsConnectionFlag, NmVlanProtocol,
NmSettingOvsIface, NmSettingOvsOtherConfig, NmSettingOvsPatch,
NmSettingOvsPort, NmSettingSriov, NmSettingSriovVf, NmSettingSriovVfVlan,
NmSettingUser, NmSettingVeth, NmSettingVlan, NmSettingVrf, NmSettingVxlan,
NmSettingWired, NmSettingsConnectionFlag, NmVlanProtocol,
};
#[cfg(feature = "query_apply")]
pub use self::device::{NmDevice, NmDeviceState, NmDeviceStateReason};

View File

@ -14,8 +14,8 @@ use super::{
loopback::gen_nm_loopback_setting,
mptcp::apply_mptcp_conf,
ovs::{
create_ovs_port_nm_conn, gen_nm_ovs_br_setting,
gen_nm_ovs_ext_ids_setting, gen_nm_ovs_iface_setting,
create_ovs_port_nm_conn, gen_nm_iface_ovs_db_setting,
gen_nm_ovs_br_setting, gen_nm_ovs_iface_setting,
},
sriov::gen_nm_sriov_setting,
user::gen_nm_user_setting,
@ -124,7 +124,7 @@ pub(crate) fn iface_to_nm_connections(
{
gen_nm_wired_setting(iface, &mut nm_conn);
}
gen_nm_ovs_ext_ids_setting(iface, &mut nm_conn);
gen_nm_iface_ovs_db_setting(iface, &mut nm_conn);
gen_nm_802_1x_setting(iface, &mut nm_conn);
gen_nm_user_setting(iface, &mut nm_conn);
gen_ethtool_setting(iface, &mut nm_conn)?;

View File

@ -5,14 +5,14 @@ use std::iter::FromIterator;
use super::super::nm_dbus::{
NmConnection, NmRange, NmSettingOvsDpdk, NmSettingOvsExtIds,
NmSettingOvsIface, NmSettingOvsPatch,
NmSettingOvsIface, NmSettingOvsOtherConfig, NmSettingOvsPatch,
};
use super::super::settings::connection::gen_nm_conn_setting;
use crate::{
BaseInterface, BridgePortTunkTag, Interface, InterfaceType, NmstateError,
OvsBridgeBondMode, OvsBridgeInterface, OvsBridgePortConfig, OvsInterface,
UnknownInterface,
OvsBridgeBondMode, OvsBridgeInterface, OvsBridgePortConfig,
OvsDbIfaceConfig, OvsInterface, UnknownInterface,
};
pub(crate) fn create_ovs_port_nm_conn(
@ -58,6 +58,10 @@ pub(crate) fn create_ovs_port_nm_conn(
if let Some(bond_updelay) = bond_conf.bond_updelay {
nm_ovs_port_set.up_delay = Some(bond_updelay);
}
if let Some(ovsdb_conf) = bond_conf.ovsdb.as_ref() {
apply_iface_ovsdb_conf(ovsdb_conf, &mut nm_conn);
}
}
if let Some(vlan_conf) = port_conf.vlan.as_ref() {
if let Some(tag) = vlan_conf.tag {
@ -168,21 +172,42 @@ pub(crate) fn gen_nm_ovs_iface_setting(
}
}
pub(crate) fn gen_nm_ovs_ext_ids_setting(
fn apply_iface_ovsdb_conf(conf: &OvsDbIfaceConfig, nm_conn: &mut NmConnection) {
let external_ids = conf.get_external_ids();
let other_config = conf.get_other_config();
if !(external_ids.is_empty() && nm_conn.ovs_ext_ids.is_none()) {
let mut nm_setting = NmSettingOvsExtIds::default();
nm_setting.data = Some(HashMap::from_iter(
external_ids
.iter()
.map(|(k, v)| (k.to_string(), v.to_string())),
));
nm_conn.ovs_ext_ids = Some(nm_setting);
}
// Do not create new setting for empty other_config unless pre-exist.
if !(other_config.is_empty() && nm_conn.ovs_other_config.is_none()) {
let mut nm_setting = NmSettingOvsOtherConfig::default();
nm_setting.data = Some(HashMap::from_iter(
other_config
.iter()
.map(|(k, v)| (k.to_string(), v.to_string())),
));
nm_conn.ovs_other_config = Some(nm_setting);
}
}
pub(crate) fn gen_nm_iface_ovs_db_setting(
iface: &Interface,
nm_conn: &mut NmConnection,
) {
if iface.iface_type() != InterfaceType::OvsBridge
&& iface.base_iface().controller_type != Some(InterfaceType::OvsBridge)
{
nm_conn.ovs_other_config = None;
nm_conn.ovs_ext_ids = None;
} else if let Some(conf) = iface.base_iface().ovsdb.as_ref() {
let mut nm_setting = NmSettingOvsExtIds::default();
nm_setting.data = Some(HashMap::from_iter(
conf.get_external_ids()
.iter()
.map(|(k, v)| (k.to_string(), v.to_string())),
));
nm_conn.ovs_ext_ids = Some(nm_setting);
apply_iface_ovsdb_conf(conf, nm_conn);
}
}

View File

@ -56,10 +56,21 @@ pub struct OvsDbIfaceConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub external_ids: Option<HashMap<String, Option<String>>>,
#[serde(skip_serializing_if = "Option::is_none")]
/// OpenvSwitch specific `other_config`. Please refer to
/// manpage `ovs-vswitchd.conf.db(5)` for more detail.
/// When setting to None, nmstate will try to preserve current
/// `other_config`, otherwise, nmstate will override all `other_config`
/// for specified interface.
pub other_config: Option<HashMap<String, Option<String>>>,
}
impl OvsDbIfaceConfig {
pub(crate) fn empty() -> Self {
Self {
external_ids: Some(HashMap::new()),
other_config: Some(HashMap::new()),
}
}
pub(crate) fn get_external_ids(&self) -> HashMap<&str, &str> {
let mut ret = HashMap::new();
if let Some(eids) = self.external_ids.as_ref() {
@ -71,6 +82,18 @@ impl OvsDbIfaceConfig {
}
ret
}
pub(crate) fn get_other_config(&self) -> HashMap<&str, &str> {
let mut ret = HashMap::new();
if let Some(cfgs) = self.other_config.as_ref() {
for (k, v) in cfgs {
if let Some(v) = v {
ret.insert(k.as_str(), v.as_str());
}
}
}
ret
}
}
impl<'de> Deserialize<'de> for OvsDbIfaceConfig {

View File

@ -180,6 +180,13 @@ fn parse_ovs_bond_conf(
if v == 0 { None } else { Some(v as u32) };
}
}
let external_ids = HashMap::from_iter(
ovsdb_port
.external_ids
.clone()
.drain()
.map(|(k, v)| (k, Some(v))),
);
let other_config = HashMap::from_iter(
ovsdb_port
@ -188,7 +195,12 @@ fn parse_ovs_bond_conf(
.drain()
.map(|(k, v)| (k, Some(v))),
);
bond_conf.other_config = Some(other_config);
if !external_ids.is_empty() || !other_config.is_empty() {
bond_conf.ovsdb = Some(OvsDbIfaceConfig {
external_ids: Some(external_ids),
other_config: Some(other_config),
});
}
bond_conf.ports = Some(bond_port_confs);
bond_conf
@ -381,10 +393,12 @@ fn ovsdb_iface_to_nmstate(
.drain()
.map(|(k, v)| (k, Some(v))),
);
iface.base_iface_mut().ovsdb = Some(OvsDbIfaceConfig {
external_ids: Some(external_ids),
other_config: Some(other_config),
});
if !external_ids.is_empty() || !other_config.is_empty() {
iface.base_iface_mut().ovsdb = Some(OvsDbIfaceConfig {
external_ids: Some(external_ids),
other_config: Some(other_config),
});
}
Some(iface)
}

View File

@ -330,6 +330,7 @@ class OVSBridge(Bridge, OvsDB):
class LinkAggregation:
MODE = "mode"
PORT_SUBTREE = "port"
OVS_DB_SUBTREE = "ovs-db"
class Port:
NAME = "name"

View File

@ -67,7 +67,6 @@ OVS_BOND_YAML_STATE = f"""
- name: {BOND1}
link-aggregation:
mode: active-backup
other_config: {EMPTY_MAP}
port:
- name: {ETH1}
- name: {ETH2}
@ -564,8 +563,7 @@ def test_ovsdb_remove_external_ids(ovs_bridge_with_custom_external_ids):
}
)
iface_info = statelib.show_only((PORT1,))[Interface.KEY][0]
external_ids = iface_info[OvsDB.OVS_DB_SUBTREE][OvsDB.EXTERNAL_IDS]
assert len(external_ids) == 0
assert OvsDB.OVS_DB_SUBTREE not in iface_info
def test_ovsdb_override_external_ids(ovs_bridge_with_custom_external_ids):
@ -1645,3 +1643,162 @@ def test_ovs_detach_2_ports_from_4_ports_ovs_bond(
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
# OpenStack use case
@pytest.mark.tier1
@pytest.mark.skipif(
nm_minor_version() < 41,
reason="OVS interface level other_config is not supported in NM 1.40-",
)
def test_ovs_bond_other_config_and_remove(
cleanup_ovs_bridge, eth1_up, eth2_up
):
desired_state = yaml.load(
"""---
interfaces:
- name: br0
type: ovs-bridge
state: up
bridge:
port:
- name: ovs0
- name: bond0
link-aggregation:
mode: balance-slb
port:
- name: eth1
- name: eth2
ovs-db:
external_ids:
test_str: foo1
test_num: 100
other_config:
bond-miimon-interval: 100
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
desired_state = yaml.load(
"""---
interfaces:
- name: br0
type: ovs-bridge
state: up
bridge:
port:
- name: ovs0
- name: bond0
link-aggregation:
mode: balance-slb
port:
- name: eth1
- name: eth2
ovs-db: {}
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
# OpenStack use case
@pytest.mark.tier1
@pytest.mark.skipif(
nm_minor_version() < 41,
reason="OVS interface level other_config is not supported in NM 1.40-",
)
def test_ovs_bridge_other_config_and_remove(
cleanup_ovs_bridge, eth1_up, eth2_up
):
desired_state = yaml.load(
"""---
interfaces:
- name: br0
type: ovs-bridge
state: up
ovs-db:
other_config:
in-band-queue: 12
bridge:
port:
- name: ovs0
- name: bond0
link-aggregation:
mode: balance-slb
port:
- name: eth1
- name: eth2
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
desired_state = yaml.load(
"""---
interfaces:
- name: br0
type: ovs-bridge
state: up
ovs-db: {}
bridge:
port:
- name: ovs0
- name: bond0
link-aggregation:
mode: balance-slb
port:
- name: eth1
- name: eth2
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
# OpenStack use case
@pytest.mark.tier1
@pytest.mark.skipif(
nm_minor_version() < 41,
reason="OVS interface level other_config is not supported in NM 1.40-",
)
def test_ovs_sys_iface_other_config_and_remove(
cleanup_ovs_bridge, eth1_up, eth2_up
):
desired_state = yaml.load(
"""---
interfaces:
- name: eth1
type: ethernet
state: up
ovs-db:
other_config:
emc-insert-inv-prob: 90
- name: br0
type: ovs-bridge
state: up
bridge:
port:
- name: ovs0
- name: eth1
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)
desired_state = yaml.load(
"""---
interfaces:
- name: eth1
type: ethernet
state: up
ovs-db: {}
""",
Loader=yaml.SafeLoader,
)
libnmstate.apply(desired_state)
assertlib.assert_state_match(desired_state)

View File

@ -29,6 +29,7 @@ from libnmstate.schema import InfiniBand
from libnmstate.schema import Interface
from libnmstate.schema import InterfaceType
from libnmstate.schema import LinuxBridge as LB
from libnmstate.schema import OVSBridge
from libnmstate.schema import OvsDB
from . import statelib
@ -115,7 +116,7 @@ def _prepare_state_for_verify(desired_state_data):
full_desired_state.remove_absent_entries()
full_desired_state.normalize()
_fix_bond_state(current_state)
_fix_ovsdb_external_ids(full_desired_state)
_sanitize_ovsdb(full_desired_state)
_remove_iface_state_for_verify(full_desired_state)
_remove_iface_state_for_verify(current_state)
_expand_vlan_filter_range(current_state)
@ -174,13 +175,34 @@ def _fix_bond_state(current_state):
bond_options["arp_ip_target"] = ""
def _fix_ovsdb_external_ids(state):
def _stringlize_ovsdb_conf(ovsdb_conf):
for prop_name in [OvsDB.EXTERNAL_IDS, OvsDB.OTHER_CONFIG]:
settings = ovsdb_conf.get(prop_name, {})
for key, value in settings.items():
settings[key] = str(value)
def _sanitize_ovsdb(state):
for iface_state in state.state[Interface.KEY]:
external_ids = iface_state.get(OvsDB.OVS_DB_SUBTREE, {}).get(
OvsDB.EXTERNAL_IDS, {}
)
for key, value in external_ids.items():
external_ids[key] = str(value)
ovsdb_conf = iface_state.get(OvsDB.OVS_DB_SUBTREE, {})
_stringlize_ovsdb_conf(ovsdb_conf)
if len(ovsdb_conf) == 0:
iface_state.pop(OvsDB.OVS_DB_SUBTREE, None)
# Convert OVS bond ovs-db value to string
for iface_state in state.state[Interface.KEY]:
for port_conf in iface_state.get(OVSBridge.CONFIG_SUBTREE, {}).get(
OVSBridge.PORT_SUBTREE, []
):
bond_conf = port_conf.get(
OVSBridge.Port.LINK_AGGREGATION_SUBTREE, {}
)
ovsdb_conf = bond_conf.get(
OVSBridge.Port.LinkAggregation.OVS_DB_SUBTREE, {}
)
_stringlize_ovsdb_conf(ovsdb_conf)
if len(ovsdb_conf) == 0:
bond_conf.pop(OvsDB.OVS_DB_SUBTREE, None)
def _remove_iface_state_for_verify(state):