mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-10-30 18:17:58 +00:00
Only push updates for desired zones/venues (#80)
This commit is contained in:
@@ -143,6 +143,9 @@ public class RRMAlgorithm {
|
|||||||
* @param dryRun if set, do not apply changes
|
* @param dryRun if set, do not apply changes
|
||||||
* @param allowDefaultMode if false, "mode" argument must be present and
|
* @param allowDefaultMode if false, "mode" argument must be present and
|
||||||
* valid (returns error if invalid)
|
* valid (returns error if invalid)
|
||||||
|
* @param updateImmediately true if the method should queue the zone for
|
||||||
|
* update and interrupt the config manager thread
|
||||||
|
* to trigger immediate update
|
||||||
*
|
*
|
||||||
* @return the algorithm result, with exactly one field set ("error" upon
|
* @return the algorithm result, with exactly one field set ("error" upon
|
||||||
* failure, any others upon success)
|
* failure, any others upon success)
|
||||||
@@ -153,7 +156,8 @@ public class RRMAlgorithm {
|
|||||||
Modeler modeler,
|
Modeler modeler,
|
||||||
String zone,
|
String zone,
|
||||||
boolean dryRun,
|
boolean dryRun,
|
||||||
boolean allowDefaultMode
|
boolean allowDefaultMode,
|
||||||
|
boolean updateImmediately
|
||||||
) {
|
) {
|
||||||
AlgorithmResult result = new AlgorithmResult();
|
AlgorithmResult result = new AlgorithmResult();
|
||||||
if (name == null || args == null) {
|
if (name == null || args == null) {
|
||||||
@@ -212,11 +216,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.channelMap = optimizer.computeChannelMap();
|
result.channelMap = optimizer.computeChannelMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.channelMap
|
result.channelMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
||||||
@@ -270,11 +277,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.txPowerMap = optimizer.computeTxPowerMap();
|
result.txPowerMap = optimizer.computeTxPowerMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.txPowerMap
|
result.txPowerMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.error = String.format("Unknown algorithm: '%s'", name);
|
result.error = String.format("Unknown algorithm: '%s'", name);
|
||||||
|
|||||||
@@ -712,7 +712,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
venue,
|
venue,
|
||||||
mock,
|
mock,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -918,7 +919,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig networkConfig =
|
DeviceConfig networkConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueAllZonesAndWakeUp();
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -982,7 +983,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig zoneConfig =
|
DeviceConfig zoneConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1045,7 +1046,10 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig apConfig =
|
DeviceConfig apConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
||||||
configManager.wakeUp();
|
// TODO enable updates to device(s), not just the entire zone
|
||||||
|
final String zone =
|
||||||
|
deviceDataManager.getDeviceZone(serialNumber);
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1118,7 +1122,10 @@ public class ApiServer implements Runnable {
|
|||||||
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
||||||
.apply(apConfig);
|
.apply(apConfig);
|
||||||
});
|
});
|
||||||
configManager.wakeUp();
|
final String zone =
|
||||||
|
deviceDataManager.getDeviceZone(serialNumber);
|
||||||
|
// TODO enable updates to device(s), not just the entire zone
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1261,7 +1268,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -1372,7 +1380,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ package com.facebook.openwifirrm.modules;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -63,8 +66,11 @@ public class ConfigManager implements Runnable {
|
|||||||
/** Is the main thread sleeping? */
|
/** Is the main thread sleeping? */
|
||||||
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
||||||
|
|
||||||
/** Was a manual config update requested? */
|
/**
|
||||||
private final AtomicBoolean eventFlag = new AtomicBoolean(false);
|
* Thread-safe set of zones for which manual config updates have been
|
||||||
|
* requested.
|
||||||
|
*/
|
||||||
|
private Set<String> zonesToUpdate = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
/** Config listener interface. */
|
/** Config listener interface. */
|
||||||
public interface ConfigListener {
|
public interface ConfigListener {
|
||||||
@@ -180,7 +186,10 @@ public class ConfigManager implements Runnable {
|
|||||||
List<String> devicesNeedingUpdate = new ArrayList<>();
|
List<String> devicesNeedingUpdate = new ArrayList<>();
|
||||||
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
||||||
params.configDebounceIntervalSec * 1_000_000_000L;
|
params.configDebounceIntervalSec * 1_000_000_000L;
|
||||||
final boolean isEvent = eventFlag.getAndSet(false);
|
Set<String> zonesToUpdateCopy = new HashSet<>(zonesToUpdate);
|
||||||
|
// use removeAll() instead of clear() in case items are added between
|
||||||
|
// the previous line and the following line
|
||||||
|
zonesToUpdate.removeAll(zonesToUpdateCopy);
|
||||||
for (DeviceWithStatus device : devices) {
|
for (DeviceWithStatus device : devices) {
|
||||||
// Update config structure
|
// Update config structure
|
||||||
DeviceData data = deviceDataMap.computeIfAbsent(
|
DeviceData data = deviceDataMap.computeIfAbsent(
|
||||||
@@ -201,11 +210,13 @@ public class ConfigManager implements Runnable {
|
|||||||
for (ConfigListener listener : configListeners.values()) {
|
for (ConfigListener listener : configListeners.values()) {
|
||||||
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
||||||
}
|
}
|
||||||
|
// Check if there are requested updates for this zone
|
||||||
// Check event flag
|
String deviceZone =
|
||||||
|
deviceDataManager.getDeviceZone(device.serialNumber);
|
||||||
|
boolean isEvent = zonesToUpdateCopy.contains(deviceZone);
|
||||||
if (params.configOnEventOnly && !isEvent) {
|
if (params.configOnEventOnly && !isEvent) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Skipping config for {} (event flag not set)",
|
"Skipping config for {} (zone not marked for updates)",
|
||||||
device.serialNumber
|
device.serialNumber
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -251,15 +262,16 @@ public class ConfigManager implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty();
|
||||||
// Send config changes to devices
|
// Send config changes to devices
|
||||||
if (!params.configEnabled) {
|
if (!params.configEnabled) {
|
||||||
logger.trace("Config changes are disabled.");
|
logger.trace("Config changes are disabled.");
|
||||||
} else if (devicesNeedingUpdate.isEmpty()) {
|
} else if (devicesNeedingUpdate.isEmpty()) {
|
||||||
logger.debug("No device configs to send.");
|
logger.debug("No device configs to send.");
|
||||||
} else if (params.configOnEventOnly && !isEvent) {
|
} else if (params.configOnEventOnly && !shouldUpdate) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
logger.error(
|
logger.error(
|
||||||
"ERROR!! {} device(s) queued for config update, but event flag not set",
|
"ERROR!! {} device(s) queued for config update, but no zones queued for update.",
|
||||||
devicesNeedingUpdate.size()
|
devicesNeedingUpdate.size()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -364,9 +376,38 @@ public class ConfigManager implements Runnable {
|
|||||||
return (configListeners.remove(id) != null);
|
return (configListeners.remove(id) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interrupt the main thread, possibly triggering an update immediately. */
|
/**
|
||||||
public void wakeUp() {
|
* Mark the zone to be updated, then interrupt the main thread to possibly
|
||||||
eventFlag.set(true);
|
* trigger an update immediately.
|
||||||
|
*
|
||||||
|
* @param zone non-null zone (i.e., venue)
|
||||||
|
*/
|
||||||
|
public void queueZoneAndWakeUp(String zone) {
|
||||||
|
if (zone == null) {
|
||||||
|
logger.debug("Zone to queue must be a non-null String.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zonesToUpdate.add(zone);
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track all zones to be updated, then interrupt the main thread to possibly
|
||||||
|
* trigger an update immediately.
|
||||||
|
*/
|
||||||
|
public void queueAllZonesAndWakeUp() {
|
||||||
|
/*
|
||||||
|
* Note, addAll is not atomic, but that is ok. This just means that it
|
||||||
|
* is possible that some zones may get updated now by the main thread
|
||||||
|
* while others get updated either when the main thread is woken up or
|
||||||
|
* the next time the main thread does its periodic update.
|
||||||
|
*/
|
||||||
|
zonesToUpdate.addAll(deviceDataManager.getZones());
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Interrupt the main thread to possibly trigger an update immediately. */
|
||||||
|
private void wakeUp() {
|
||||||
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
||||||
wakeupFlag.set(true);
|
wakeupFlag.set(true);
|
||||||
mainThread.interrupt();
|
mainThread.interrupt();
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifirrm.modules;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import org.quartz.CronScheduleBuilder;
|
|
||||||
import org.quartz.CronExpression;
|
import org.quartz.CronExpression;
|
||||||
|
import org.quartz.CronScheduleBuilder;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
import org.quartz.JobBuilder;
|
import org.quartz.JobBuilder;
|
||||||
import org.quartz.JobDetail;
|
import org.quartz.JobDetail;
|
||||||
@@ -332,7 +332,8 @@ public class RRMScheduler {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
params.dryRun,
|
params.dryRun,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
false /* updateImmediately */
|
||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
"'{}' result for zone '{}': {}",
|
"'{}' result for zone '{}': {}",
|
||||||
@@ -341,5 +342,6 @@ public class RRMScheduler {
|
|||||||
gson.toJson(result)
|
gson.toJson(result)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -638,14 +638,13 @@ public abstract class ChannelOptimizer {
|
|||||||
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program the given channel map into the AP config and notify the config
|
* Program the given channel map into the AP config.
|
||||||
* manager.
|
|
||||||
*
|
*
|
||||||
* @param deviceDataManager the DeviceDataManager instance
|
* @param deviceDataManager the DeviceDataManager instance
|
||||||
* @param configManager the ConfigManager instance
|
* @param configManager the ConfigManager instance
|
||||||
* @param channelMap the map of devices (by serial number) to radio to channel
|
* @param channelMap the map of devices (by serial number) to radio to channel
|
||||||
*/
|
*/
|
||||||
public void applyConfig(
|
public void updateDeviceApConfig(
|
||||||
DeviceDataManager deviceDataManager,
|
DeviceDataManager deviceDataManager,
|
||||||
ConfigManager configManager,
|
ConfigManager configManager,
|
||||||
Map<String, Map<String, Integer>> channelMap
|
Map<String, Map<String, Integer>> channelMap
|
||||||
@@ -663,8 +662,5 @@ public abstract class ChannelOptimizer {
|
|||||||
deviceConfig.autoChannels = entry.getValue();
|
deviceConfig.autoChannels = entry.getValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger config update now
|
|
||||||
configManager.wakeUp();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,14 +151,13 @@ public abstract class TPC {
|
|||||||
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
|
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program the given tx power map into the AP config and notify the config
|
* Program the given tx power map into the AP config.
|
||||||
* manager.
|
|
||||||
*
|
*
|
||||||
* @param deviceDataManager the DeviceDataManager instance
|
* @param deviceDataManager the DeviceDataManager instance
|
||||||
* @param configManager the ConfigManager instance
|
* @param configManager the ConfigManager instance
|
||||||
* @param txPowerMap the map of devices (by serial number) to radio to tx power
|
* @param txPowerMap the map of devices (by serial number) to radio to tx power
|
||||||
*/
|
*/
|
||||||
public void applyConfig(
|
public void updateDeviceApConfig(
|
||||||
DeviceDataManager deviceDataManager,
|
DeviceDataManager deviceDataManager,
|
||||||
ConfigManager configManager,
|
ConfigManager configManager,
|
||||||
Map<String, Map<String, Integer>> txPowerMap
|
Map<String, Map<String, Integer>> txPowerMap
|
||||||
@@ -176,9 +175,6 @@ public abstract class TPC {
|
|||||||
deviceConfig.autoTxPowers = entry.getValue();
|
deviceConfig.autoTxPowers = entry.getValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger config update now
|
|
||||||
configManager.wakeUp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user