Blame SOURCES/BZ_1819738-Don-t-merge-bond-options-when-bond-mode-changed.patch

8f4f2d
From 238fb9542c4365ae13f6b4f0c2cd6ffcd427e561 Mon Sep 17 00:00:00 2001
8f4f2d
From: Gris Ge <fge@redhat.com>
8f4f2d
Date: Wed, 1 Apr 2020 20:26:40 +0800
8f4f2d
Subject: [PATCH] bond: Don't merge bond options when bond mode changed
8f4f2d
8f4f2d
When changing bond mode, user is required to provide all bond options
8f4f2d
and no bond options will be merged from current state.
8f4f2d
8f4f2d
When changing bond mode, full deactivation is required in NetworkManager
8f4f2d
do to bug https://bugzilla.redhat.com/show_bug.cgi?id=1819137
8f4f2d
8f4f2d
Procedure to fix this issue:
8f4f2d
    * Generate a metadata if bond mode changed.
8f4f2d
    * After merge, use desire bond option only if bond mode changed.
8f4f2d
    * In NM profile modification, deactivate the interface before
8f4f2d
      modification if bond mode changed.
8f4f2d
8f4f2d
Added integration test case and unit test cases.
8f4f2d
8f4f2d
Signed-off-by: Gris Ge <fge@redhat.com>
8f4f2d
---
8f4f2d
 libnmstate/appliers/bond.py | 62 +++++++++++++++++++++++++++++++++++++
8f4f2d
 libnmstate/metadata.py      |  2 ++
8f4f2d
 libnmstate/nm/applier.py    | 18 +++++++++++
8f4f2d
 libnmstate/state.py         |  9 ++++++
8f4f2d
 4 files changed, 91 insertions(+)
8f4f2d
8f4f2d
diff --git a/libnmstate/appliers/bond.py b/libnmstate/appliers/bond.py
8f4f2d
index 5602adb..2a3f0a1 100644
8f4f2d
--- a/libnmstate/appliers/bond.py
8f4f2d
+++ b/libnmstate/appliers/bond.py
8f4f2d
@@ -18,9 +18,16 @@
8f4f2d
 #
8f4f2d
 
8f4f2d
 import contextlib
8f4f2d
+import logging
8f4f2d
 
8f4f2d
 from libnmstate.schema import Bond
8f4f2d
 from libnmstate.schema import BondMode
8f4f2d
+from libnmstate.schema import Interface
8f4f2d
+from libnmstate.schema import InterfaceState
8f4f2d
+from libnmstate.schema import InterfaceType
8f4f2d
+
8f4f2d
+BOND_MODE_CHANGED_METADATA = "_bond_mode_changed"
8f4f2d
+NON_UP_STATES = (InterfaceState.DOWN, InterfaceState.ABSENT)
8f4f2d
 
8f4f2d
 
8f4f2d
 class BondNamedOptions:
8f4f2d
@@ -126,3 +133,58 @@ def fix_bond_option_arp_monitor(cur_iface_state):
8f4f2d
         and "arp_ip_target" not in bond_options
8f4f2d
     ):
8f4f2d
         bond_options["arp_ip_target"] = ""
8f4f2d
+
8f4f2d
+
8f4f2d
+def generate_bond_mode_change_metadata(desire_state, current_state):
8f4f2d
+    for iface_name, iface_state in desire_state.interfaces.items():
8f4f2d
+        current_iface_state = current_state.interfaces.get(iface_name, {})
8f4f2d
+        if (
8f4f2d
+            iface_state.get(
8f4f2d
+                Interface.TYPE, current_iface_state.get(Interface.TYPE)
8f4f2d
+            )
8f4f2d
+            != InterfaceType.BOND
8f4f2d
+        ):
8f4f2d
+            continue
8f4f2d
+        if iface_state.get(Interface.STATE) in NON_UP_STATES:
8f4f2d
+            # Ignore bond mode change on absent/down interface
8f4f2d
+            continue
8f4f2d
+        current_bond_mode = current_iface_state.get(
8f4f2d
+            Bond.CONFIG_SUBTREE, {}
8f4f2d
+        ).get(Bond.MODE)
8f4f2d
+        desire_bond_mode = iface_state.get(Bond.CONFIG_SUBTREE, {}).get(
8f4f2d
+            Bond.MODE
8f4f2d
+        )
8f4f2d
+        if (
8f4f2d
+            desire_bond_mode
8f4f2d
+            and current_bond_mode
8f4f2d
+            and desire_bond_mode != current_bond_mode
8f4f2d
+        ):
8f4f2d
+            logging.warning(
8f4f2d
+                "Discarding all current bond options as interface "
8f4f2d
+                f"{iface_name} has bond mode changed"
8f4f2d
+            )
8f4f2d
+            iface_state[BOND_MODE_CHANGED_METADATA] = True
8f4f2d
+
8f4f2d
+
8f4f2d
+def remove_bond_mode_change_metadata(iface_state):
8f4f2d
+    iface_state.pop(BOND_MODE_CHANGED_METADATA, None)
8f4f2d
+
8f4f2d
+
8f4f2d
+def is_bond_mode_changed(iface_state):
8f4f2d
+    return iface_state.get(BOND_MODE_CHANGED_METADATA)
8f4f2d
+
8f4f2d
+
8f4f2d
+def discard_merged_data_on_mode_change(merged_iface_state, desire_iface_state):
8f4f2d
+    """
8f4f2d
+    When bond mode changed, use original desire bond options instead of merging
8f4f2d
+    from current state.
8f4f2d
+    """
8f4f2d
+    if is_bond_mode_changed(merged_iface_state):
8f4f2d
+        if merged_iface_state.get(Bond.CONFIG_SUBTREE, {}).get(
8f4f2d
+            Bond.OPTIONS_SUBTREE
8f4f2d
+        ):
8f4f2d
+            merged_iface_state[Bond.CONFIG_SUBTREE][
8f4f2d
+                Bond.OPTIONS_SUBTREE
8f4f2d
+            ] = desire_iface_state[Bond.CONFIG_SUBTREE].get(
8f4f2d
+                Bond.OPTIONS_SUBTREE, {}
8f4f2d
+            )
8f4f2d
diff --git a/libnmstate/metadata.py b/libnmstate/metadata.py
8f4f2d
index b268ba3..bdc8b3c 100644
8f4f2d
--- a/libnmstate/metadata.py
8f4f2d
+++ b/libnmstate/metadata.py
8f4f2d
@@ -53,6 +53,7 @@ def generate_ifaces_metadata(desired_state, current_state):
8f4f2d
     metadata is generated on interfaces, usable by the provider when
8f4f2d
     configuring the interface.
8f4f2d
     """
8f4f2d
+    bond.generate_bond_mode_change_metadata(desired_state, current_state)
8f4f2d
     _generate_link_master_metadata(
8f4f2d
         desired_state.interfaces,
8f4f2d
         current_state.interfaces,
8f4f2d
@@ -91,6 +92,7 @@ def remove_ifaces_metadata(ifaces_state):
8f4f2d
         iface_state.pop(MASTER, None)
8f4f2d
         iface_state.pop(MASTER_TYPE, None)
8f4f2d
         iface_state.pop(BRPORT_OPTIONS, None)
8f4f2d
+        bond.remove_bond_mode_change_metadata(iface_state)
8f4f2d
         iface_state.get(Interface.IPV4, {}).pop(ROUTES, None)
8f4f2d
         iface_state.get(Interface.IPV6, {}).pop(ROUTES, None)
8f4f2d
         iface_state.get(Interface.IPV4, {}).pop(DNS_METADATA, None)
8f4f2d
diff --git a/libnmstate/nm/applier.py b/libnmstate/nm/applier.py
8f4f2d
index 1291469..1ca0647 100644
8f4f2d
--- a/libnmstate/nm/applier.py
8f4f2d
+++ b/libnmstate/nm/applier.py
8f4f2d
@@ -18,6 +18,7 @@
8f4f2d
 #
8f4f2d
 
8f4f2d
 import base64
8f4f2d
+import logging
8f4f2d
 import hashlib
8f4f2d
 import itertools
8f4f2d
 from operator import attrgetter
8f4f2d
@@ -28,6 +29,7 @@ from libnmstate.schema import InterfaceState
8f4f2d
 from libnmstate.schema import InterfaceType
8f4f2d
 from libnmstate.schema import LinuxBridge as LB
8f4f2d
 from libnmstate.schema import OVSBridge as OvsB
8f4f2d
+from libnmstate.appliers.bond import is_bond_mode_changed
8f4f2d
 
8f4f2d
 from . import bond
8f4f2d
 from . import bridge
8f4f2d
@@ -169,6 +171,7 @@ def set_ifaces_admin_state(ifaces_desired_state, con_profiles=()):
8f4f2d
     master_ifaces_to_edit = set()
8f4f2d
     ifaces_to_edit = set()
8f4f2d
     remove_devs_actions = {}
8f4f2d
+    devs_to_deactivate_beforehand = []
8f4f2d
 
8f4f2d
     for iface_desired_state in ifaces_desired_state:
8f4f2d
         ifname = iface_desired_state[Interface.NAME]
8f4f2d
@@ -199,6 +202,18 @@ def set_ifaces_admin_state(ifaces_desired_state, con_profiles=()):
8f4f2d
                     new_ifaces_to_activate.add(ifname)
8f4f2d
         else:
8f4f2d
             if iface_desired_state[Interface.STATE] == InterfaceState.UP:
8f4f2d
+                if is_bond_mode_changed(iface_desired_state):
8f4f2d
+                    # NetworkManager leaves leftover in sysfs for bond
8f4f2d
+                    # options when changing bond mode, bug:
8f4f2d
+                    # https://bugzilla.redhat.com/show_bug.cgi?id=1819137
8f4f2d
+                    # Workaround: delete the bond interface from kernel and
8f4f2d
+                    # create again via full deactivation beforehand.
8f4f2d
+                    logging.debug(
8f4f2d
+                        f"Bond interface {ifname} is changing bond mode, "
8f4f2d
+                        "will do full deactivation before applying changes"
8f4f2d
+                    )
8f4f2d
+                    devs_to_deactivate_beforehand.append(nmdev)
8f4f2d
+
8f4f2d
                 if _is_master_iface(iface_desired_state):
8f4f2d
                     master_ifaces_to_edit.add(
8f4f2d
                         (nmdev, con_profiles_by_devname[ifname].profile)
8f4f2d
@@ -230,6 +245,9 @@ def set_ifaces_admin_state(ifaces_desired_state, con_profiles=()):
8f4f2d
                     )
8f4f2d
                 )
8f4f2d
 
8f4f2d
+    for dev in devs_to_deactivate_beforehand:
8f4f2d
+        device.deactivate(dev)
8f4f2d
+
8f4f2d
     # Do not remove devices that are marked for editing.
8f4f2d
     for dev, _ in itertools.chain(master_ifaces_to_edit, ifaces_to_edit):
8f4f2d
         remove_devs_actions.pop(dev, None)
8f4f2d
diff --git a/libnmstate/state.py b/libnmstate/state.py
8f4f2d
index 98be33d..12bc414 100644
8f4f2d
--- a/libnmstate/state.py
8f4f2d
+++ b/libnmstate/state.py
8f4f2d
@@ -390,10 +390,19 @@ class State:
8f4f2d
         entries that appear only on one state are ignored.
8f4f2d
         This is a reverse recursive update operation.
8f4f2d
         """
8f4f2d
+        origin_self_state = State(self.state)
8f4f2d
         other_state = State(other_state.state)
8f4f2d
         for name in self.interfaces.keys() & other_state.interfaces.keys():
8f4f2d
             dict_update(other_state.interfaces[name], self.interfaces[name])
8f4f2d
             self._ifaces_state[name] = other_state.interfaces[name]
8f4f2d
+            iface_state = self.interfaces[name]
8f4f2d
+            if iface_state.get(Interface.TYPE) == InterfaceType.BOND:
8f4f2d
+                origin_self_iface_state = origin_self_state.interfaces.get(
8f4f2d
+                    name
8f4f2d
+                )
8f4f2d
+                bond.discard_merged_data_on_mode_change(
8f4f2d
+                    iface_state, origin_self_iface_state
8f4f2d
+                )
8f4f2d
 
8f4f2d
     def complement_master_interfaces_removal(self, other_state):
8f4f2d
         """
8f4f2d
-- 
8f4f2d
2.18.2
8f4f2d