Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nm): Added modem reset timer when connection is failed #5644

Merged
merged 11 commits into from
Jan 15, 2025
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -20,10 +20,12 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Timer;

import org.eclipse.kura.nm.enums.MMModemLocationSource;
import org.eclipse.kura.nm.enums.MMModemState;
import org.eclipse.kura.nm.signal.handlers.NMModemResetHandler;
import org.eclipse.kura.nm.signal.handlers.NMModemResetTimerTask;
import org.eclipse.kura.nm.status.SimProperties;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
Expand All @@ -49,6 +51,7 @@ public class ModemManagerDbusWrapper {
private final DBusConnection dbusConnection;

private final Map<String, NMModemResetHandler> modemHandlers = new HashMap<>();
private final Map<String, MMFailedModemResetTimer> failedModemResetTimers = new HashMap<>();

public ModemManagerDbusWrapper(DBusConnection dbusConnection) {
this.dbusConnection = dbusConnection;
Expand Down Expand Up @@ -142,6 +145,15 @@ public MMModemState getMMModemState(Properties modemProperties) {
return MMModemState.toMMModemState(modemProperties.Get(MM_MODEM_NAME, MM_MODEM_PROPERTY_STATE));
}

public MMModemState getMMModemState(String modemPath) throws DBusException {
Optional<Properties> properties = getModemProperties(modemPath);
if (properties.isPresent()) {
return getMMModemState(properties.get());
} else {
return MMModemState.MM_MODEM_STATE_UNKNOWN;
}
}

public Optional<Properties> getModemProperties(String modemPath) throws DBusException {
Optional<Properties> modemProperties = Optional.empty();
Properties properties = this.dbusConnection.getRemoteObject(MM_BUS_NAME, modemPath, Properties.class);
Expand Down Expand Up @@ -275,4 +287,82 @@ protected void resetHandlersDisable(String deviceId) {
this.modemHandlers.remove(deviceId);
}
}

protected void failedModemResetTimerSchedule(String deviceId, Optional<String> modemManagerDbusPath, int delayMinutes)
throws DBusException {
if (!modemManagerDbusPath.isPresent()) {
logger.warn("Cannot retrieve modem device for {}. Skipping modem reset monitor setup.", deviceId);
return;
}

Modem mmModemDevice = this.dbusConnection.getRemoteObject(MM_BUS_NAME, modemManagerDbusPath.get(), Modem.class);

MMFailedModemResetTimer resetTimer = new MMFailedModemResetTimer(mmModemDevice, delayMinutes);
resetTimer.schedule();

this.failedModemResetTimers.put(deviceId, resetTimer);
}

protected void failedModemResetTimerCancel() {
for (String deviceId : this.failedModemResetTimers.keySet()) {
failedModemResetTimerCancel(deviceId);
}
this.modemHandlers.clear();
}

protected void failedModemResetTimerCancel(String deviceId) {
if (this.failedModemResetTimers.containsKey(deviceId)) {
MMFailedModemResetTimer timer = this.failedModemResetTimers.get(deviceId);
timer.cancel();
}
}

protected boolean isMMFailedModemResetTimerArmed(String deviceId) {
return this.failedModemResetTimers.containsKey(deviceId);
}

private class MMFailedModemResetTimerTask extends NMModemResetTimerTask {

public MMFailedModemResetTimerTask(Modem modem) {
super(modem);
}

@Override
public void run() {
try {
MMModemState modemState = getMMModemState(this.getModemDbusPath());
if (MMModemState.MM_MODEM_STATE_FAILED.equals(modemState)) {
super.run();
} else {
NMModemResetTimerTask.logger.info("Modem state changed. Reset skipped.");
}
} catch (DBusException e) {
NMModemResetTimerTask.logger.warn("Couldn't get state of modem interface, caused by:", e);
}
}

}

private class MMFailedModemResetTimer {

private final Timer timer = new Timer("FailedModemResetTimer");
private final MMFailedModemResetTimerTask task;
private final long delay;

public MMFailedModemResetTimer(Modem modem, long delayMinutes) {
this.delay = delayMinutes * 60L * 1000L;
this.task = new MMFailedModemResetTimerTask(modem);
}

public void schedule() {
this.timer.schedule(this.task, this.delay);
}

public void cancel() {
if (this.task != null) {
this.task.cancel();
}
this.timer.cancel();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -35,6 +35,7 @@
import org.eclipse.kura.net.wifi.WifiSecurity;
import org.eclipse.kura.nm.configuration.NMSettingsConverter;
import org.eclipse.kura.nm.enums.MMModemLocationSource;
import org.eclipse.kura.nm.enums.MMModemState;
import org.eclipse.kura.nm.enums.NMDeviceState;
import org.eclipse.kura.nm.enums.NMDeviceType;
import org.eclipse.kura.nm.signal.handlers.DeviceCreationLock;
Expand Down Expand Up @@ -133,11 +134,15 @@ public void setSystemService(SystemService systemService) {
this.optionalSystemService = Optional.of(systemService);
}

public boolean configurationEnforcementIsActive() {
protected boolean configurationEnforcementIsActive() {
return Objects.nonNull(this.configurationEnforcementHandler) && Objects.nonNull(this.deviceAddedHandler)
&& this.configurationEnforcementHandlerIsArmed;
}

protected boolean failedModemResetTimerIsActive(String modemId) {
return this.modemManager.isMMFailedModemResetTimerArmed(modemId);
}

public void checkPermissions() {
Map<String, String> getPermissions = this.networkManager.getPermissions();
if (logger.isDebugEnabled()) {
Expand Down Expand Up @@ -396,7 +401,7 @@ private synchronized void doApply(Map<String, Object> networkConfiguration) thro
logger.info("Applying configuration using NetworkManager Dbus connector");
NetworkProperties properties = new NetworkProperties(networkConfiguration);
List<String> availableDeviceIds = getInterfaceIds();
Set<String> availableDevices = new LinkedHashSet<String>(availableDeviceIds);
Set<String> availableDevices = new LinkedHashSet<>(availableDeviceIds);
Optional<List<String>> configuredInterfaceIds = properties.getOptStringList("net.interfaces");
if (configuredInterfaceIds.isPresent()) {
availableDevices.addAll(configuredInterfaceIds.get());
Expand Down Expand Up @@ -556,11 +561,14 @@ private void enableInterface(String deviceId, NetworkProperties properties, Devi
connection = Optional.of(createdConnection);
}

boolean activationSucceeded = true;
try {
this.modemManager.failedModemResetTimerCancel(deviceId);
this.networkManager.activateConnection(connection.get(), device);
dsLock.waitForSignal();
} catch (DBusExecutionException e) {
logger.warn("Couldn't complete activation of {} interface, caused by:", deviceId, e);
activationSucceeded = false;
}

// Housekeeping
Expand All @@ -573,15 +581,32 @@ private void enableInterface(String deviceId, NetworkProperties properties, Devi

if (deviceType == NMDeviceType.NM_DEVICE_TYPE_MODEM) {
int delayMinutes = properties.get(Integer.class, "net.interface.%s.config.resetTimeout", deviceId);
Optional<String> mmDbusPath = this.networkManager.getModemManagerDbusPath(device.getObjectPath());

if (delayMinutes == 0 || !mmDbusPath.isPresent()) {
return;
}

if (delayMinutes != 0) {
Optional<String> mmDbusPath = this.networkManager.getModemManagerDbusPath(device.getObjectPath());
this.modemManager.resetHandlerEnable(deviceId, mmDbusPath, delayMinutes, device.getObjectPath());
this.modemManager.resetHandlerEnable(deviceId, mmDbusPath, delayMinutes, device.getObjectPath());

// If a modem connection fails at the first try, it stays in the failed state, thus not triggering the usual
// modem reset procedure. So, start a reset timer here.
if (!activationSucceeded && isModemFailed(mmDbusPath.get())) {
logger.info("Modem {} in failed state or unavailable. Scheduling modem reset in {} minutes ...",
device.getObjectPath(), delayMinutes);

this.modemManager.failedModemResetTimerSchedule(deviceId, mmDbusPath, delayMinutes);
}

}

}

private boolean isModemFailed(String mmDbusPath) throws DBusException {
MMModemState modemState = this.modemManager.getMMModemState(mmDbusPath);
return MMModemState.MM_MODEM_STATE_FAILED.equals(modemState);
}

private void createVirtualInterface(String deviceId, NetworkProperties properties, NMDeviceType deviceType)
throws DBusException {
Map<String, Map<String, Variant<?>>> newConnectionSettings = NMSettingsConverter.buildSettings(properties,
Expand Down Expand Up @@ -643,6 +668,7 @@ private void disable(Optional<Device> optDevice, String deviceId) throws DBusExc
logger.warn("Can't disable missing device {}", deviceId);
return;
}
this.modemManager.failedModemResetTimerCancel(deviceId);
Device device = optDevice.get();
Optional<Connection> appliedConnection = this.networkManager.getAppliedConnection(device);

Expand Down Expand Up @@ -730,4 +756,5 @@ private List<String> getModemsPaths() {

return modemsPath;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023 Eurotech and/or its affiliates and others
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023 Eurotech and/or its affiliates and others
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -22,7 +22,7 @@

public class NMModemResetTimerTask extends TimerTask {

private static final Logger logger = LoggerFactory.getLogger(NMModemResetTimerTask.class);
protected static final Logger logger = LoggerFactory.getLogger(NMModemResetTimerTask.class);

private final Modem modem;
private boolean expired = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -1127,6 +1127,41 @@ public void shouldNotApplyWPA2WPA3WiFiConfigurationIfWPA3IsNotSupported() throws
thenActivateConnectionIsNotCalledFor("wlan0");
}

@Test
public void shouldNotStartFailedModemResetTimerIfConnectionSucceeds() throws DBusException, IOException {
givenBasicMockedDbusConnector();
givenMockedDevice("1-6", "wwan0", NMDeviceType.NM_DEVICE_TYPE_MODEM, NMDeviceState.NM_DEVICE_STATE_ACTIVATED,
true, false, false);
givenMockedDeviceList();
givenNetworkConfigMapWith("net.interfaces", "1-6");
givenNetworkConfigMapWith("net.interface.1-6.config.resetTimeout", 2);
givenNetworkConfigMapWith("net.interface.1-6.config.dhcpClient4.enabled", true);
givenNetworkConfigMapWith("net.interface.1-6.config.ip4.status", "netIPv4StatusEnabledWAN");

whenApplyIsCalledWith(this.netConfig);

thenNoExceptionIsThrown();
thenFailedModemResetTimerIsActive(false, "1-6");
}

@Test
public void shouldStartFailedModemResetTimerIfConnectionFails() throws DBusException, IOException {
givenBasicMockedDbusConnector();
givenNMActivationFailed();
givenMockedDevice("1-6", "wwan0", NMDeviceType.NM_DEVICE_TYPE_MODEM, NMDeviceState.NM_DEVICE_STATE_FAILED, true,
false, false);
givenMockedDeviceList();
givenNetworkConfigMapWith("net.interfaces", "1-6");
givenNetworkConfigMapWith("net.interface.1-6.config.resetTimeout", 2);
givenNetworkConfigMapWith("net.interface.1-6.config.dhcpClient4.enabled", true);
givenNetworkConfigMapWith("net.interface.1-6.config.ip4.status", "netIPv4StatusEnabledWAN");

whenApplyIsCalledWith(this.netConfig);

thenNoExceptionIsThrown();
thenFailedModemResetTimerIsActive(true, "1-6");
}

/*
* Given
*/
Expand Down Expand Up @@ -1154,6 +1189,11 @@ private void givenBasicMockedDbusConnector() throws DBusException, IOException {

}

private void givenNMActivationFailed() {
when(this.mockedNetworkManager.ActivateConnection(any(), any(), any()))
.thenThrow(new DBusExecutionException("Activation Failed!"));
}

private void givenMockedPermissions() {

Map<String, String> tempPerms = new HashMap<>();
Expand Down Expand Up @@ -1426,7 +1466,7 @@ private void givenModemMocksFor(String deviceId, String interfaceName, Propertie
.thenReturn(Arrays.asList(new UInt32[] { new UInt32(40), new UInt32(69) }));
when(modemProperties.Get(MM_MODEM_BUS_NAME, "PrimarySimSlot")).thenReturn(new UInt32(0));
when(modemProperties.Get(MM_MODEM_BUS_NAME, "UnlockRequired")).thenReturn(new UInt32(1));
when(modemProperties.Get(MM_MODEM_BUS_NAME, "State")).thenReturn(8);
when(modemProperties.Get(MM_MODEM_BUS_NAME, "State")).thenReturn(-1);
when(modemProperties.Get(MM_MODEM_BUS_NAME, "AccessTechnologies")).thenReturn(new UInt32(0));
when(modemProperties.Get(MM_MODEM_BUS_NAME, "SignalQuality"))
.thenReturn(new UInt32[] { new UInt32(97), new UInt32(2) });
Expand Down Expand Up @@ -1813,7 +1853,7 @@ private void thenModemStatusHasCorrectValues(boolean hasBearers, boolean hasSims
assertTrue(modemStatus.isGpsSupported());
assertEquals(EnumSet.of(ModemGpsMode.UNMANAGED, ModemGpsMode.MANAGED_GPS), modemStatus.getSupporteGpsModes());
assertFalse(modemStatus.isSimLocked());
assertEquals(ModemConnectionStatus.REGISTERED, modemStatus.getConnectionStatus());
assertEquals(ModemConnectionStatus.FAILED, modemStatus.getConnectionStatus());
mattdibi marked this conversation as resolved.
Show resolved Hide resolved
assertEquals(1, modemStatus.getAccessTechnologies().size());
assertTrue(modemStatus.getAccessTechnologies().contains(AccessTechnology.UNKNOWN));
assertEquals(97, modemStatus.getSignalQuality());
Expand Down Expand Up @@ -1851,6 +1891,10 @@ private void thenDeviceExists(String interfaceName) {
assertTrue(this.mockDevices.containsKey(interfaceName));
}

public void thenFailedModemResetTimerIsActive(boolean expectedValue, String modemId) {
assertEquals(expectedValue, this.instanceNMDbusConnector.failedModemResetTimerIsActive(modemId));
}

private void simulateIwCommandOutputs(String interfaceName, Properties preMockedProperties)
throws IOException, DBusException {
Wireless wirelessDevice = mock(Wireless.class);
Expand Down
Loading