fix loop endless
This commit is contained in:
@@ -199,12 +199,17 @@ class BridgeLinkStateWatcher:
|
||||
self._evaluate_bridge_state(reason="recovery_deadline")
|
||||
|
||||
def _evaluate_bridge_state(self, reason: str, source_ifname: Optional[str] = None) -> None:
|
||||
with self._lock:
|
||||
previous_states = dict(self._member_states)
|
||||
|
||||
members = [iface for iface in get_bridge_ports_once(self.bridge_name) if check_interface_exists(iface)]
|
||||
states = {iface: self._read_member_state(iface) for iface in members}
|
||||
states = {
|
||||
iface: self._read_member_state(iface, previous_states.get(iface))
|
||||
for iface in members
|
||||
}
|
||||
now = time.time()
|
||||
|
||||
with self._lock:
|
||||
previous_states = dict(self._member_states)
|
||||
self._last_event_ts = now
|
||||
self._last_error = None
|
||||
self._member_states = states
|
||||
@@ -389,7 +394,10 @@ class BridgeLinkStateWatcher:
|
||||
if current is None or previous is None:
|
||||
return False
|
||||
|
||||
return current.mtu != previous.mtu or current.ethernet_profile != previous.ethernet_profile
|
||||
return current.mtu != previous.mtu or self._profiles_differ(
|
||||
current.ethernet_profile,
|
||||
previous.ethernet_profile,
|
||||
)
|
||||
|
||||
def _state_has_usable_config(self, state: MemberLinkState) -> bool:
|
||||
"""Return whether this snapshot contains configuration that can be mirrored."""
|
||||
@@ -469,28 +477,128 @@ class BridgeLinkStateWatcher:
|
||||
except OSError:
|
||||
return
|
||||
|
||||
def _read_member_state(self, ifname: str) -> MemberLinkState:
|
||||
def _read_member_state(
|
||||
self,
|
||||
ifname: str,
|
||||
previous_state: Optional[MemberLinkState] = None,
|
||||
) -> MemberLinkState:
|
||||
admin_up = read_interface_admin_up(ifname)
|
||||
carrier_up = read_interface_carrier(ifname)
|
||||
operstate = read_interface_operstate(ifname)
|
||||
link_ready = self._link_looks_ready(admin_up, carrier_up, operstate)
|
||||
|
||||
return MemberLinkState(
|
||||
ifname=ifname,
|
||||
admin_up=read_interface_admin_up(ifname),
|
||||
carrier_up=read_interface_carrier(ifname),
|
||||
operstate=read_interface_operstate(ifname),
|
||||
admin_up=admin_up,
|
||||
carrier_up=carrier_up,
|
||||
operstate=operstate,
|
||||
mtu=read_interface_mtu(ifname),
|
||||
ethernet_profile=self._read_ethernet_profile(ifname),
|
||||
ethernet_profile=self._read_ethernet_profile(
|
||||
ifname,
|
||||
previous_state=previous_state,
|
||||
link_ready=link_ready,
|
||||
),
|
||||
)
|
||||
|
||||
def _read_ethernet_profile(self, ifname: str) -> Optional[EthernetProfile]:
|
||||
def _read_ethernet_profile(
|
||||
self,
|
||||
ifname: str,
|
||||
previous_state: Optional[MemberLinkState] = None,
|
||||
link_ready: Optional[bool] = None,
|
||||
) -> Optional[EthernetProfile]:
|
||||
"""Read ethtool speed/duplex/autoneg for one interface if supported."""
|
||||
profile = read_interface_ethernet_profile(ifname)
|
||||
if profile is None:
|
||||
return None
|
||||
|
||||
return EthernetProfile(
|
||||
current_profile = None
|
||||
else:
|
||||
current_profile = EthernetProfile(
|
||||
speed_mbps=profile.get("speed_mbps"),
|
||||
duplex=profile.get("duplex"),
|
||||
autoneg=profile.get("autoneg"),
|
||||
)
|
||||
|
||||
if self._should_keep_previous_profile(ifname, current_profile, previous_state, link_ready):
|
||||
return previous_state.ethernet_profile if previous_state is not None else None
|
||||
|
||||
previous_profile = previous_state.ethernet_profile if previous_state is not None else None
|
||||
return self._materialize_profile(current_profile, previous_profile)
|
||||
|
||||
def _should_keep_previous_profile(
|
||||
self,
|
||||
ifname: str,
|
||||
current_profile: Optional[EthernetProfile],
|
||||
previous_state: Optional[MemberLinkState],
|
||||
link_ready: Optional[bool],
|
||||
) -> bool:
|
||||
"""Keep the previous non-null profile during transient renegotiation windows."""
|
||||
if previous_state is None or previous_state.ethernet_profile is None:
|
||||
return False
|
||||
if current_profile is not None and self._partial_profile_is_usable(current_profile):
|
||||
return False
|
||||
if link_ready is True:
|
||||
return False
|
||||
|
||||
now = time.time()
|
||||
with self._lock:
|
||||
settling = self._settle_deadlines.get(ifname, 0.0) > now
|
||||
managed = self._managed_event_deadlines.get(ifname, 0.0) > now
|
||||
|
||||
return settling or managed or link_ready is False
|
||||
|
||||
def _link_looks_ready(
|
||||
self,
|
||||
admin_up: Optional[bool],
|
||||
carrier_up: Optional[bool],
|
||||
operstate: Optional[str],
|
||||
) -> bool:
|
||||
"""Evaluate link health from sysfs fields before the MemberLinkState is built."""
|
||||
if admin_up is not True:
|
||||
return False
|
||||
if carrier_up is False:
|
||||
return False
|
||||
if operstate in {"down", "lowerlayerdown", "notpresent"}:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _profiles_differ(
|
||||
self,
|
||||
current_profile: Optional[EthernetProfile],
|
||||
previous_profile: Optional[EthernetProfile],
|
||||
) -> bool:
|
||||
"""Ignore transient non-null to null drops when detecting config changes."""
|
||||
if current_profile is None:
|
||||
return False
|
||||
if previous_profile is None:
|
||||
return True
|
||||
return current_profile != previous_profile
|
||||
|
||||
def _partial_profile_is_usable(self, profile: Optional[EthernetProfile]) -> bool:
|
||||
"""Return whether a profile contains enough data to drive synchronization."""
|
||||
if profile is None:
|
||||
return False
|
||||
if profile.autoneg is True:
|
||||
return True
|
||||
return profile.autoneg is False and profile.speed_mbps is not None and profile.duplex is not None
|
||||
|
||||
def _materialize_profile(
|
||||
self,
|
||||
current_profile: Optional[EthernetProfile],
|
||||
previous_profile: Optional[EthernetProfile],
|
||||
) -> Optional[EthernetProfile]:
|
||||
"""Fill transiently missing profile fields from the last stable snapshot."""
|
||||
if current_profile is None:
|
||||
return previous_profile
|
||||
if previous_profile is None:
|
||||
return current_profile
|
||||
if self._partial_profile_is_usable(current_profile):
|
||||
return current_profile
|
||||
|
||||
return EthernetProfile(
|
||||
speed_mbps=current_profile.speed_mbps if current_profile.speed_mbps is not None else previous_profile.speed_mbps,
|
||||
duplex=current_profile.duplex if current_profile.duplex is not None else previous_profile.duplex,
|
||||
autoneg=current_profile.autoneg if current_profile.autoneg is not None else previous_profile.autoneg,
|
||||
)
|
||||
|
||||
def _sync_member_configuration(self, source_ifname: str, states: dict[str, MemberLinkState]) -> None:
|
||||
"""Mirror MTU and ethtool link settings from one healthy member to its siblings."""
|
||||
source = states[source_ifname]
|
||||
|
||||
Reference in New Issue
Block a user