6 Commits

Author SHA1 Message Date
TIP Automation User
a25b28e7a7 Chg: update image tag in helm values to v2.7.0-RC3 2022-10-01 00:58:23 +00:00
RockyMandayam2
c3b51aeafd [WIFI-10943] Deal with "auto" value for channel and fix 80p80 representation (#92) (#93)
Co-authored-by: Jun Woo Shin <jwoos@users.noreply.github.com>
2022-09-30 18:23:26 -04:00
TIP Automation User
bb4d3368a0 Chg: update image tag in helm values to v2.7.0-RC2 2022-09-29 23:27:51 +00:00
RockyMandayam2
7d70bfd650 Use short instead of byte to store unsigned byte values in VHTOperationElement (#81) (#85)
Signed-off-by: Rocky Mandayam <rockymandayam@fb.com>

Signed-off-by: Rocky Mandayam <rockymandayam@fb.com>
2022-09-27 14:11:32 -04:00
Jun Woo Shin
4373036d51 [WIFI-10805] Make AP-AP TPC algorithm use tx power from statistics and fix TPC application to correct band (#76) (#83)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>

Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-09-22 16:59:19 -04:00
TIP Automation User
84b896f939 Chg: update image tag in helm values to v2.7.0-RC1 2022-09-16 19:55:12 +00:00
140 changed files with 1620 additions and 4717 deletions

View File

@@ -18,7 +18,7 @@ jobs:
distribution: 'adopt'
cache: maven
- name: Build with Maven
run: mvn javadoc:aggregate
run: mvn javadoc:javadoc
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:

11
.gitignore vendored
View File

@@ -1,9 +1,12 @@
/target/
*/target/
/target
/*.log*
/device_config.json
/settings.json
/topology.json
# Eclipse
.settings/
bin/
/.settings/
/bin/
.metadata
.classpath
.project

View File

@@ -1,7 +1,7 @@
FROM maven:3-eclipse-temurin-11 as build
FROM maven:3-jdk-11 as build
WORKDIR /usr/src/java
COPY . .
RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)"
RUN mvn clean package -DappendVersionString="$(./scripts/get_build_version.sh)"
FROM adoptopenjdk/openjdk11-openj9:latest
RUN apt-get update && apt-get install -y gettext-base wget
@@ -10,8 +10,8 @@ RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentr
RUN mkdir /owrrm-data
WORKDIR /usr/src/java
COPY docker-entrypoint.sh /
COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/
EXPOSE 16789 16790
COPY --from=build /usr/src/java/target/openwifi-rrm.jar /usr/local/bin/
EXPOSE 16789
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \
"-jar", "/usr/local/bin/openwifi-rrm.jar", \

View File

@@ -1,10 +1,12 @@
# OpenWiFi RRM Service
[See here](owrrm/README.md) for details.
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK.
## Project Structure
This is an [Apache Maven] project with the following modules:
* `lib-cloudsdk` - OpenWiFi CloudSDK Java Library
* `owrrm` - OpenWiFi RRM Service
This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats,
capabilities) via the uCentral Gateway and Kafka, and integrates with the
OpenWiFi Provisioning service to perform optimization across configured
"venues". It pushes new device configuration parameters to APs after RRM
algorithms are run (manually or periodically).
## Requirements
* **Running:** JRE 11.
@@ -14,7 +16,7 @@ This is an [Apache Maven] project with the following modules:
```
$ mvn package [-DskipTests]
```
This will build a runnable JAR located at `owrrm/target/openwifi-rrm.jar`.
This will build a runnable JAR located at `target/openwifi-rrm.jar`.
Alternatively, Docker builds can be launched using the provided
[Dockerfile](Dockerfile).
@@ -25,7 +27,34 @@ $ mvn test
```
Unit tests are written using [JUnit 5].
## Code Style
## Usage
```
$ java -jar openwifi-rrm.jar [-h]
```
To start the service, use the `run` command while providing configuration via
either environment variables (`--config-env`) or a static JSON file
(`--config-file`, default `settings.json`). The following data is *required*:
* Service configuration
* Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT`
* JSON: `serviceConfig` structure
* Kafka broker URL
* Env: `KAFKACONFIG_BOOTSTRAPSERVER`
* JSON: `kafkaConfig` structure
* MySQL database credentials
* Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD`
* JSON: `databaseConfig` structure
## OpenAPI
This service provides an OpenAPI HTTP interface on the port specified in the
service configuration (`moduleConfig.apiServerParams`). An auto-generated
OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is
written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase.
## For Developers
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and
[ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms.
Code is auto-formatted using [Spotless] with a custom Eclipse style config (see
[spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)).
This can be applied via Maven (but is *not* enforced at build time):

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -9,7 +9,7 @@ fullnameOverride: ""
images:
owrrm:
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm
tag: main
tag: v2.7.0-RC3
pullPolicy: Always
# regcred:
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
@@ -29,8 +29,8 @@ services:
targetPort: 16789
protocol: TCP
restapiinternal:
servicePort: 16790
targetPort: 16790
servicePort: 17007
targetPort: 17007
protocol: TCP
checks:

View File

@@ -1,3 +0,0 @@
# OpenWiFi CloudSDK Java Library
A Java library providing clients and models for the OpenWiFi uCentral-based
CloudSDK.

View File

@@ -1,80 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>openwifi-cloudsdk</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.facebook</groupId>
<artifactId>openwifi-base</artifactId>
<version>2.7.0</version>
</parent>
<properties>
<!-- Hack for static files located in root project -->
<myproject.root>${project.basedir}/..</myproject.root>
</properties>
<build>
<finalName>openwifi-cloudsdk</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,201 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association;
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association.Rate;
/**
* Aggregation model for State aggregation. Only contains info useful for
* analysis.
*/
public class AggregatedState {
/** Rate information with aggregated fields. */
public static class AggregatedRate {
/**
* This is the common bitRate for all the aggregated fields.
*/
public long bitRate;
/**
* This is the common channel width for all the aggregated fields.
*/
public int chWidth;
/**
* Aggregated fields mcs
*/
public List<Integer> mcs = new ArrayList<>();
/** Constructor with no args */
private AggregatedRate() {}
/** Add a Rate to the AggregatedRate */
private void add(Rate rate) {
if (rate == null) {
return;
}
if (mcs.isEmpty()) {
bitRate = rate.bitrate;
chWidth = rate.chwidth;
}
mcs.add(rate.mcs);
}
/**
* Add an AggregatedRate with the same channel_width to the
* AggregatedRate
*/
private void add(AggregatedRate rate) {
if (rate == null || rate.chWidth != chWidth) {
return;
}
if (mcs.isEmpty()) {
bitRate = rate.bitRate;
chWidth = rate.chWidth;
}
mcs.addAll(rate.mcs);
}
}
/**
* Radio information with channel, channel_width and tx_power.
*/
public static class Radio {
public int channel;
public int channelWidth;
public int txPower;
private Radio() {}
public Radio(int channel, int channelWidth, int txPower) {
this.channel = channel;
this.channelWidth = channelWidth;
this.txPower = txPower;
}
private Radio(Map<String, Integer> radioInfo) {
channel = radioInfo.getOrDefault("channel", -1);
channelWidth = radioInfo.getOrDefault("channel_width", -1);
txPower = radioInfo.getOrDefault("tx_power", -1);
}
@Override
public int hashCode() {
return Objects.hash(channel, channelWidth, txPower);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Radio other = (Radio) obj;
return channel == other.channel &&
channelWidth == other.channelWidth && txPower == other.txPower;
}
}
public String bssid;
public String station;
public long connected;
public long inactive;
public List<Integer> rssi;
public long rxBytes;
public long rxPackets;
public AggregatedRate rxRate;
public long txBytes;
public long txDuration;
public long txFailed;
public long txPackets;
public AggregatedRate txRate;
public long txRetries;
public int ackSignal;
public int ackSignalAvg;
public Radio radio;
/** Constructor with no args */
public AggregatedState() {
this.rxRate = new AggregatedRate();
this.txRate = new AggregatedRate();
this.rssi = new ArrayList<>();
this.radio = new Radio();
}
/** Construct from Association and radio */
public AggregatedState(
Association association,
Map<String, Integer> radioInfo
) {
this.rxRate = new AggregatedRate();
this.txRate = new AggregatedRate();
this.rssi = new ArrayList<>();
this.bssid = association.bssid;
this.station = association.station;
this.connected = association.connected;
this.inactive = association.inactive;
this.rssi.add(association.rssi);
this.rxBytes = association.rx_bytes;
this.rxPackets = association.rx_packets;
this.rxRate.add(association.rx_rate);
this.txBytes = association.tx_bytes;
this.txDuration = association.tx_duration;
this.txFailed = association.tx_failed;
this.txPackets = association.tx_packets;
this.txRate.add(association.tx_rate);
this.txRetries = association.tx_retries;
this.ackSignal = association.ack_signal;
this.ackSignalAvg = association.ack_signal_avg;
this.radio = new Radio(radioInfo);
}
/**
* Check whether the passed-in AggregatedState and this one match for aggregation.
* If the two match in bssid, station and radio. Then they could be aggregated.
*
* @param state the reference AggregatedState with which to check with.
* @return boolean return true if the two matches for aggregation.
*/
public boolean matchesForAggregation(AggregatedState state) {
return bssid == state.bssid && station == state.station &&
Objects.equals(radio, state.radio);
}
/**
* Add an AggregatedState to this AggregatedState. Succeed only when the two
* match for aggregation.
*
* @param state input AggregatedState
* @return boolean true if the two match in bssid, station, channel,
* channel_width and tx_power
*/
public boolean add(AggregatedState state) {
if (matchesForAggregation(state)) {
this.rssi.addAll(state.rssi);
this.rxRate.add(state.rxRate);
this.txRate.add(state.txRate);
return true;
}
return false;
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
import java.util.Objects;
import com.facebook.openwifi.cloudsdk.ies.Country;
import com.facebook.openwifi.cloudsdk.ies.LocalPowerConstraint;
import com.facebook.openwifi.cloudsdk.ies.QbssLoad;
import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo;
/** Wrapper class containing information elements */
public final class InformationElements {
public Country country;
public QbssLoad qbssLoad;
public LocalPowerConstraint localPowerConstraint;
public TxPwrInfo txPwrInfo;
@Override
public int hashCode() {
return Objects.hash(country, localPowerConstraint, qbssLoad, txPwrInfo);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
InformationElements other = (InformationElements) obj;
return Objects.equals(country, other.country) && Objects.equals(
localPowerConstraint,
other.localPowerConstraint
) && Objects.equals(qbssLoad, other.qbssLoad) &&
Objects.equals(txPwrInfo, other.txPwrInfo);
}
}

View File

@@ -1,156 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries.
* Refer to the 802.11 specification for more details. Language in
* javadocs is taken from the specification.
*/
public class Country {
/** Defined in 802.11 */
public static final int TYPE = 7;
/** Constraints for a subset of channels in the AP's country */
public static class CountryInfo {
/**
* The lowest channel number in the CountryInfo.
*/
public final int firstChannelNumber;
/**
* The maximum power, in dBm, allowed to be transmitted.
*/
public final int maximumTransmitPowerLevel;
/**
* Number of channels this CountryInfo applies to. E.g., if First
* Channel Number is 2 and Number of Channels is 4, this CountryInfo
* describes channels 2, 3, 4, and 5.
*/
public final int numberOfChannels;
/** Constructor. */
public CountryInfo(
int firstChannelNumber,
int maximumTransmitPowerLevel,
int numberOfChannels
) {
this.firstChannelNumber = firstChannelNumber;
this.maximumTransmitPowerLevel = maximumTransmitPowerLevel;
this.numberOfChannels = numberOfChannels;
}
/** Parse CountryInfo from the appropriate Json object. */
public static CountryInfo parse(JsonObject contents) {
final int firstChannelNumber =
contents.get("First Channel Number").getAsInt();
final int maximumTransmitPowerLevel = contents
.get("Maximum Transmit Power Level (in dBm)")
.getAsInt();
final int numberOfChannels =
contents.get("Number of Channels").getAsInt();
return new CountryInfo(
firstChannelNumber,
maximumTransmitPowerLevel,
numberOfChannels
);
}
@Override
public int hashCode() {
return Objects.hash(
firstChannelNumber,
maximumTransmitPowerLevel,
numberOfChannels
);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
CountryInfo other = (CountryInfo) obj;
return firstChannelNumber == other.firstChannelNumber &&
maximumTransmitPowerLevel == other.maximumTransmitPowerLevel &&
numberOfChannels == other.numberOfChannels;
}
@Override
public String toString() {
return "CountryInfo [firstChannelNumber=" + firstChannelNumber +
", maximumTransmitPowerLevel=" + maximumTransmitPowerLevel +
", numberOfChannels=" + numberOfChannels + "]";
}
}
/**
* Each constraint is a CountryInfo describing tx power constraints on
* one or more channels, for the current country.
*/
public final List<CountryInfo> constraints;
/** Constructor */
public Country(List<CountryInfo> countryInfos) {
this.constraints = Collections.unmodifiableList(countryInfos);
}
/** Parse Country IE from the appropriate Json object. */
public static Country parse(JsonObject contents) {
List<CountryInfo> constraints = new ArrayList<>();
JsonElement constraintsObject = contents.get("constraints");
if (constraintsObject != null) {
for (JsonElement jsonElement : constraintsObject.getAsJsonArray()) {
JsonObject innerElem = jsonElement.getAsJsonObject();
CountryInfo countryInfo =
CountryInfo.parse(innerElem.get("Country Info").getAsJsonObject());
constraints.add(countryInfo);
}
}
return new Country(constraints);
}
@Override
public int hashCode() {
return Objects.hash(constraints);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Country other = (Country) obj;
return Objects.equals(constraints, other.constraints);
}
@Override
public String toString() {
return "Country [constraints=" + constraints + "]";
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries. It is called
* "Local Power Constraint" in these entries, and just "Power Constraint" in
* the 802.11 specification. Refer to the specification for more details.
* Language in javadocs is taken from the specification.
*/
public class LocalPowerConstraint {
/** Defined in 802.11 */
public static final int TYPE = 32;
/**
* Units are dB.
* <p>
* The local maximum transmit power for a channel is defined as the maximum
* transmit power level specified for the channel in the Country IE minus
* this variable for the given channel.
*/
public final int localPowerConstraint;
/** Constructor */
public LocalPowerConstraint(int localPowerConstraint) {
this.localPowerConstraint = localPowerConstraint;
}
/** Parse LocalPowerConstraint IE from appropriate Json object. */
public static LocalPowerConstraint parse(JsonObject contents) {
final int localPowerConstraint =
contents.get("Local Power Constraint").getAsInt();
return new LocalPowerConstraint(localPowerConstraint);
}
@Override
public int hashCode() {
return Objects.hash(localPowerConstraint);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
LocalPowerConstraint other = (LocalPowerConstraint) obj;
return localPowerConstraint == other.localPowerConstraint;
}
@Override
public String toString() {
return "LocalPowerConstraint [localPowerConstraint=" +
localPowerConstraint + "]";
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries. It is called
* "QBSS Load" in these entries, and just "BSS Load" in the 802.11
* specification. Refer to the specification for more details. Language in
* javadocs is taken from the specification.
*/
public class QbssLoad {
/** Defined in 802.11 */
public static final int TYPE = 11;
/**
* The total number of STAs currently associated with the BSS.
*/
public final int stationCount;
/**
* The Channel Utilization field is defined as the percentage of time,
* linearly scaled with 255 representing 100%, that the AP sensed the
* medium was busy, as indicated by either the physical or virtual carrier
* sense (CS) mechanism. When more than one channel is in use for the BSS,
* the Channel Utilization field value is calculated only for the primary
* channel. This percentage is computed using the following formula:
* <p>
* floor(255 * channelBusyTime /
* (dot11ChannelUtilizationBeaconIntervals * dot11BeaconPeriod * 1024)
* )
*/
public final int channelUtilization;
/**
* The Available Admission Capacity field contains an unsigned integer that
* specifies the remaining amount of medium time available via explicit
* admission control, in units of 32 miscrosecond/second. The field is
* helpful for roaming STAs to select an AP that is likely to accept future
* admission control requests, but it does not represent an assurance that
* the HC admits these requests.
*/
public final int availableAdmissionCapacity;
/** Constructor */
public QbssLoad(
int stationCount,
int channelUtilization,
int availableAdmissionCapacity
) {
this.stationCount = stationCount;
this.channelUtilization = channelUtilization;
this.availableAdmissionCapacity = availableAdmissionCapacity;
}
/** Parse QbssLoad IE from appropriate Json object; return null if invalid. */
public static QbssLoad parse(JsonObject contents) {
// unclear why there is this additional nested layer
JsonElement ccaContentJsonElement = contents.get("802.11e CCA Version");
if (ccaContentJsonElement == null) {
return null;
}
contents = ccaContentJsonElement.getAsJsonObject();
final int stationCount = contents.get("Station Count").getAsInt();
final int channelUtilization =
contents.get("Channel Utilization").getAsInt();
final int availableAdmissionCapacity =
contents.get("Available Admission Capabilities").getAsInt();
return new QbssLoad(
stationCount,
channelUtilization,
availableAdmissionCapacity
);
}
@Override
public int hashCode() {
return Objects.hash(
availableAdmissionCapacity,
channelUtilization,
stationCount
);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
QbssLoad other = (QbssLoad) obj;
return availableAdmissionCapacity == other.availableAdmissionCapacity &&
channelUtilization == other.channelUtilization &&
stationCount == other.stationCount;
}
@Override
public String toString() {
return "QbssLoad [stationCount=" + stationCount +
", channelUtilization=" + channelUtilization +
", availableAdmissionCapacity=" + availableAdmissionCapacity + "]";
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries. It is called
* "Tx Pwr Info" in these entries, and "Transmit Power Envelope" in the 802.11
* specification. Refer to the specification for more details. Language in
* javadocs is taken from the specification.
*/
public class TxPwrInfo {
/** Defined in 802.11 */
public static final int TYPE = 195;
/** Local maximum transmit power for 20 MHz. Required field. */
public final int localMaxTxPwrConstraint20MHz;
/** Local maximum transmit power for 40 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint40MHz;
/** Local maximum transmit power for 80 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint80MHz;
/** Local maximum transmit power for both 160 MHz and 80+80 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint160MHz;
/** Constructor */
public TxPwrInfo(
int localMaxTxPwrConstraint20MHz,
Integer localMaxTxPwrConstraint40MHz,
Integer localMaxTxPwrConstraint80MHz,
Integer localMaxTxPwrConstraint160MHz
) {
this.localMaxTxPwrConstraint20MHz = localMaxTxPwrConstraint20MHz;
this.localMaxTxPwrConstraint40MHz = localMaxTxPwrConstraint40MHz;
this.localMaxTxPwrConstraint80MHz = localMaxTxPwrConstraint80MHz;
this.localMaxTxPwrConstraint160MHz = localMaxTxPwrConstraint160MHz;
}
/** Parse TxPwrInfo IE from appropriate Json object. */
public static TxPwrInfo parse(JsonObject contents) {
JsonObject innerObj = contents.get("Tx Pwr Info").getAsJsonObject();
// required field
int localMaxTxPwrConstraint20MHz =
innerObj.get("Local Max Tx Pwr Constraint 20MHz").getAsInt();
// optional field
Integer localMaxTxPwrConstraint40MHz =
parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 40MHz");
Integer localMaxTxPwrConstraint80MHz =
parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 80MHz");
Integer localMaxTxPwrConstraint160MHz =
parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 160MHz");
return new TxPwrInfo(
localMaxTxPwrConstraint20MHz,
localMaxTxPwrConstraint40MHz,
localMaxTxPwrConstraint80MHz,
localMaxTxPwrConstraint160MHz
);
}
private static Integer parseOptionalField(
JsonObject contents,
String fieldName
) {
JsonElement element = contents.get(fieldName);
if (element == null) {
return null;
}
return element.getAsInt();
}
@Override
public int hashCode() {
return Objects.hash(
localMaxTxPwrConstraint160MHz,
localMaxTxPwrConstraint20MHz,
localMaxTxPwrConstraint40MHz,
localMaxTxPwrConstraint80MHz
);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TxPwrInfo other = (TxPwrInfo) obj;
return localMaxTxPwrConstraint160MHz ==
other.localMaxTxPwrConstraint160MHz &&
localMaxTxPwrConstraint20MHz ==
other.localMaxTxPwrConstraint20MHz &&
localMaxTxPwrConstraint40MHz ==
other.localMaxTxPwrConstraint40MHz &&
localMaxTxPwrConstraint80MHz == other.localMaxTxPwrConstraint80MHz;
}
@Override
public String toString() {
return "TxPwrInfo [localMaxTxPwrConstraint20MHz=" +
localMaxTxPwrConstraint20MHz + ", localMaxTxPwrConstraint40MHz=" +
localMaxTxPwrConstraint40MHz + ", localMaxTxPwrConstraint80MHz=" +
localMaxTxPwrConstraint80MHz + ", localMaxTxPwrConstraint160MHz=" +
localMaxTxPwrConstraint160MHz + "]";
}
}

View File

@@ -1,6 +0,0 @@
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c{1}:%L] - %m%n

View File

@@ -478,10 +478,8 @@ components:
RRMSchedule:
type: object
properties:
crons:
type: array
items:
type: string
cron:
type: string
algorithms:
type: array
items:

4
owrrm/.gitignore vendored
View File

@@ -1,4 +0,0 @@
/*.log*
/device_config.json
/settings.json
/topology.json

View File

@@ -1,39 +0,0 @@
# OpenWiFi RRM Service
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK.
This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats,
capabilities) via the uCentral Gateway and Kafka, and integrates with the
OpenWiFi Provisioning service to perform optimization across configured
"venues". It pushes new device configuration parameters to APs after RRM
algorithms are run (manually or periodically).
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and
[ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms.
## Usage
```
$ java -jar openwifi-rrm.jar [-h]
```
To start the service, use the `run` command while providing configuration via
either environment variables (`--config-env`) or a static JSON file
(`--config-file`, default `settings.json`). The following data is *required*:
* Service configuration
* Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT`
* JSON: `serviceConfig` structure
* Kafka broker URL
* Env: `KAFKACONFIG_BOOTSTRAPSERVER`
* JSON: `kafkaConfig` structure
* MySQL database credentials
* Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD`
* JSON: `databaseConfig` structure
## OpenAPI
This service provides an OpenAPI HTTP interface on the port specified in the
service configuration (`moduleConfig.apiServerParams`). An auto-generated
OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is
written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase.
[JUnit 5]: https://junit.org/junit5/

View File

@@ -1,143 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>openwifi-rrm</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.facebook</groupId>
<artifactId>openwifi-base</artifactId>
<version>2.7.0</version>
</parent>
<properties>
<!-- Hack for static files located in root project -->
<myproject.root>${project.basedir}/..</myproject.root>
<mainClassName>com.facebook.openwifi.rrm.Launcher</mainClassName>
<appendVersionString></appendVersionString>
</properties>
<build>
<finalName>openwifi-rrm</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${mainClassName}</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.facebook</groupId>
<artifactId>openwifi-cloudsdk</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,160 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import spark.embeddedserver.jetty.JettyServerFactory;
import spark.utils.Assert;
/**
* Creates Jetty Server instances. Majority of the logic is taken from
* JettyServerFactory. The additional feature is that this class will actually
* set two connectors (original class doesn't set any connectors at all and
* leaves it up to the serivce start logic). Since we set two connectors here on
* the server, Spark uses the existing conectors instead of trying to spin up
* its own connectors. The other difference is that it uses a different
* ServerConnector constructor to avoid allocating additional threads that
* aren't necessary ({@link #makeConnector})
*
* @see spark.embeddedserver.jetty.EmbeddedJettyFactory
*/
public class CustomJettyServerFactory implements JettyServerFactory {
// normally this is set in EmbeddedJettyServer but since we create our own connectors here,
// we need the value here
private boolean trustForwardHeaders = true; // true by default
private final int internalPort;
private final int externalPort;
public CustomJettyServerFactory(int internalPort, int externalPort) {
this.internalPort = internalPort;
this.externalPort = externalPort;
}
public void setTrustForwardHeaders(boolean trustForwardHeaders) {
this.trustForwardHeaders = trustForwardHeaders;
}
/**
* This is basically
* spark.embeddedserver.jetty.SocketConnectorFactory.createSocketConnector,
* the only difference being that we use a different constructor for the
* Connector and that the private methods called are just inlined.
*/
public Connector makeConnector(
Server server,
String host,
int port,
boolean trustForwardHeaders
) {
Assert.notNull(server, "'server' must not be null");
Assert.notNull(host, "'host' must not be null");
// spark.embeddedserver.jetty.SocketConnectorFactory.createHttpConnectionFactory
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecureScheme("https");
if (trustForwardHeaders) {
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
}
HttpConnectionFactory httpConnectionFactory =
new HttpConnectionFactory(httpConfig);
ServerConnector connector = new ServerConnector(
server,
0, // acceptors, don't allocate separate threads for acceptor
0, // selectors, use default number
httpConnectionFactory
);
// spark.embeddedserver.jetty.SocketConnectorFactory.initializeConnector
connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1));
connector.setHost(host);
connector.setPort(port);
return connector;
}
/**
* Creates a Jetty server.
*
* @param maxThreads maxThreads
* @param minThreads minThreads
* @param threadTimeoutMillis threadTimeoutMillis
* @return a new jetty server instance
*/
@Override
public Server create(
int maxThreads,
int minThreads,
int threadTimeoutMillis
) {
Server server;
if (maxThreads > 0) {
int max = maxThreads;
int min = (minThreads > 0) ? minThreads : 8;
int idleTimeout =
(threadTimeoutMillis > 0) ? threadTimeoutMillis : 60000;
server = new Server(new QueuedThreadPool(max, min, idleTimeout));
} else {
server = new Server();
}
Connector internalConnector = null;
if (internalPort != -1) {
internalConnector = makeConnector(
server,
"0.0.0.0",
internalPort,
trustForwardHeaders
);
}
Connector externalConnector = null;
if (externalPort != -1) {
externalConnector = makeConnector(
server,
"0.0.0.0",
externalPort,
trustForwardHeaders
);
}
if (internalConnector == null) {
server.setConnectors(new Connector[] { externalConnector });
} else if (externalConnector == null) {
server.setConnectors(new Connector[] { internalConnector });
} else {
server.setConnectors(
new Connector[] { internalConnector, externalConnector }
);
}
return server;
}
/**
* Creates a Jetty server with supplied thread pool
* @param threadPool thread pool
* @return a new jetty server instance
*/
@Override
public Server create(ThreadPool threadPool) {
return threadPool != null ? new Server(threadPool) : new Server();
}
}

View File

@@ -1,273 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test;
public class RRMSchedulerTest {
@Test
void test_parseIntoQuartzCron() throws Exception {
// missing one field (requires 6, only 5 given)
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * *"));
// more than maximum number of fields (7 max, has 8)
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * * * *"));
// correct (6 fields)
assertArrayEquals(
new String[] { "* * * ? * *" },
RRMScheduler.parseIntoQuartzCron("* * * * * *")
);
// correct (7 fields)
assertArrayEquals(
new String[] { "* * * ? * * *" },
RRMScheduler.parseIntoQuartzCron("* * * * * * *")
);
// correct value other than * for day of month
assertArrayEquals(
new String[] { "* * * 1 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * *")
);
assertArrayEquals(
new String[] { "* * * 1 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * * *")
);
assertArrayEquals(
new String[] { "* * * 1/2 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * *")
);
assertArrayEquals(
new String[] { "* * * 1/2 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * * *")
);
assertArrayEquals(
new String[] { "* * * 1-2 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * *")
);
assertArrayEquals(
new String[] { "* * * 1-2 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * * *")
);
assertArrayEquals(
new String[] { "* * * 1,2 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * *")
);
assertArrayEquals(
new String[] { "* * * 1,2 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * * *")
);
// wrong value other than * for day of month - 0 is not a valid day of month
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * * *"));
// correct value other than * for day of month
assertArrayEquals(
new String[] { "* * * ? * 1" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1")
);
assertArrayEquals(
new String[] { "* * * ? * 1 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1/3" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3")
);
assertArrayEquals(
new String[] { "* * * ? * 1/3 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1-3" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3")
);
assertArrayEquals(
new String[] { "* * * ? * 1-3 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1,3" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3")
);
assertArrayEquals(
new String[] { "* * * ? * 1,3 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3 *")
);
// correct value other than * for day of month, make sure 0 turns into 7
assertArrayEquals(
new String[] { "* * * ? * 7" },
RRMScheduler.parseIntoQuartzCron("* * * * * 0")
);
assertArrayEquals(
new String[] { "* * * ? * 7 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1,7" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0")
);
assertArrayEquals(
new String[] { "* * * ? * 1,7 *" },
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0 *")
);
// wrong value other than * for day of month - 8 is not a valid day of week
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7/8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7/8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7-8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7-8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7,8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7,8 *"));
// correct value for both day of week and day of month
assertArrayEquals(
new String[] { "* * * ? * 7", "* * * 1 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 0")
);
assertArrayEquals(
new String[] { "* * * ? * 7 *", "* * * 1 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7", "* * * 1 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1/0")
);
assertArrayEquals(
new String[] { "* * * ? * 1", "* * * 1/2 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7", "* * * 1/2 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1/0")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7 *", "* * * 1 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1/0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1 *", "* * * 1/2 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7 *", "* * * 1/2 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1/0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7", "* * * 1 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1-0")
);
assertArrayEquals(
new String[] { "* * * ? * 1", "* * * 1-3 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7", "* * * 1-3 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1-0")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7 *", "* * * 1 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1-0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1 *", "* * * 1-3 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7 *", "* * * 1-3 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1-0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7", "* * * 1/3 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1/3 * 1-0")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7", "* * * 1-3 * ?" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1/0")
);
assertArrayEquals(
new String[] { "* * * ? * 1-7 *", "* * * 1/3 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1/3 * 1-0 *")
);
assertArrayEquals(
new String[] { "* * * ? * 1/7 *", "* * * 1-3 * ? *" },
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1/0 *")
);
// wrong value for either day of week or day of month
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7/8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7/8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7-8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7-8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7,8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7,8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7/8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7/8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7-8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7-8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7,8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7,8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7/8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7/8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7-8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7-8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7,8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7,8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7/8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7/8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7-8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7-8 *"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7,8"));
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7,8 *"));
}
}

View File

@@ -1,121 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.services;
import java.util.Map;
import java.util.HashMap;
import java.time.Instant;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.gw.UserInfo;
import com.facebook.openwifi.cloudsdk.models.gw.WebTokenResult;
import com.google.gson.Gson;
import spark.Service;
import spark.Request;
import spark.Response;
import spark.Route;
/**
* This is a mock OW Security service meant to be used in tests.
*
* @see <a href="https://github.com/Telecominfraproject/wlan-cloud-ucentralsec">owsec</a>
*/
public class MockOWSecService {
private class TokenInfo {
long expiry;
long created;
}
private final Gson gson = new Gson();
/** A mapping of valid tokens to their expiry time in seconds since epoch */
private Map<String, TokenInfo> validTokens;
/** The Spark service */
private Service service;
public MockOWSecService(int port) {
validTokens = new HashMap<>();
service = Service.ignite();
service.port(port);
service.get("/api/v1/validateToken", new ValidateTokenEndpoint());
service.get("/api/v1/oauth2", new ValidateTokenEndpoint());
service.get("/api/v1/systemEndpoints", new SystemEndpoint());
service.awaitInitialization();
}
public void stop() {
service.stop();
service.awaitStop();
}
public void addToken(String token, long expiresInSec) {
TokenInfo time = new TokenInfo();
time.created = Instant.now().getEpochSecond();
time.expiry = expiresInSec;
validTokens.put(token, time);
}
public void removeToken(String token) {
validTokens.remove(token);
}
public int getPort() { return service.port(); }
public class Oauth2Endpoint implements Route {
@Override
public String handle(Request request, Response response) {
response.status(501);
return "Not Implemented";
}
}
public class SystemEndpoint implements Route {
@Override
public String handle(Request request, Response response) {
response.status(501);
return "Not Implemented";
}
}
public class ValidateTokenEndpoint implements Route {
@Override
public String handle(Request request, Response response) {
String token = request.queryParams("token");
if (token == null) {
response.status(403);
return "Forbidden";
}
TokenInfo info = validTokens.get(token);
if (info == null) {
response.status(403);
return "Forbidden";
}
if (info.created + info.expiry < Instant.now().getEpochSecond()) {
response.status(403);
return "Forbidden";
}
TokenValidationResult result = new TokenValidationResult();
result.userInfo = new UserInfo();
result.tokenInfo = new WebTokenResult();
result.tokenInfo.access_token = token;
result.tokenInfo.created = info.created;
result.tokenInfo.expires_in = info.expiry;
return gson.toJson(result);
}
}
}

412
pom.xml
View File

@@ -2,220 +2,230 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.facebook</groupId>
<artifactId>openwifi-base</artifactId>
<artifactId>openwifi-rrm</artifactId>
<version>2.7.0</version>
<packaging>pom</packaging>
<modules>
<module>lib-cloudsdk</module>
<module>owrrm</module>
</modules>
<properties>
<!-- Hack for static files located in root project -->
<myproject.root>${project.basedir}</myproject.root>
<java.version>11</java.version>
<slf4j.version>1.7.32</slf4j.version>
<junit.version>5.7.2</junit.version>
<swagger.version>2.1.10</swagger.version>
<mainClassName>com.facebook.openwifirrm.Launcher</mainClassName>
<appendVersionString></appendVersionString>
<!-- do not abort builds on autoformatter errors -->
<spotless.check.skip>true</spotless.check.skip>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/*.md</exclude>
</excludes>
</filter>
<filter>
<artifact>log4j:log4j</artifact>
<excludes>
<!-- CVE-2021-4104 -->
<exclude>org/apache/log4j/net/JMSAppender.class</exclude>
<!-- CVE-2019-17571 -->
<exclude>org/apache/log4j/net/SocketServer.class</exclude>
</excludes>
</filter>
</filters>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<doclint>all,-missing</doclint>
<notimestamp>true</notimestamp>
<bottom>Copyright &#169; Meta Platforms, Inc. and affiliates.</bottom>
</configuration>
</plugin>
<plugin>
<!-- Generate "openapi.yaml" in project root, NOT packaged -->
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-maven-plugin</artifactId>
<version>${swagger.version}</version>
<configuration>
<outputFileName>openapi</outputFileName>
<outputPath>${project.basedir}</outputPath>
<outputFormat>YAML</outputFormat>
<prettyPrint>TRUE</prettyPrint>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>resolve</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.25.0</version>
<configuration>
<java>
<eclipse>
<file>${myproject.root}/spotless/eclipse-java-formatter.xml</file>
<version>4.12.0</version>
</eclipse>
<trimTrailingWhitespace />
<removeUnusedImports/>
<licenseHeader>
<file>${myproject.root}/spotless/license-header.txt</file>
</licenseHeader>
</java>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
<version>3.11.09</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<finalName>openwifi-rrm</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/*.md</exclude>
</excludes>
</filter>
<filter>
<artifact>log4j:log4j</artifact>
<excludes>
<!-- CVE-2021-4104 -->
<exclude>org/apache/log4j/net/JMSAppender.class</exclude>
<!-- CVE-2019-17571 -->
<exclude>org/apache/log4j/net/SocketServer.class</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${mainClassName}</Main-Class>
</manifestEntries>
</transformer>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<doclint>all,-missing</doclint>
<notimestamp>true</notimestamp>
<bottom>Copyright &#169; Meta Platforms, Inc. and affiliates.</bottom>
</configuration>
</plugin>
<plugin>
<!-- Generate "openapi.yaml" in project root, NOT packaged -->
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<artifactId>swagger-maven-plugin</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
<configuration>
<outputFileName>openapi</outputFileName>
<outputPath>${project.basedir}</outputPath>
<outputFormat>YAML</outputFormat>
<prettyPrint>TRUE</prettyPrint>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>resolve</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.25.0</version>
<configuration>
<java>
<eclipse>
<file>${project.basedir}/spotless/eclipse-java-formatter.xml</file>
<version>4.12.0</version>
</eclipse>
<trimTrailingWhitespace />
<removeUnusedImports/>
<licenseHeader>
<file>${project.basedir}/spotless/license-header.txt</file>
</licenseHeader>
</java>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>com.konghq</groupId>
<artifactId>unirest-java</artifactId>
<version>3.11.09</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
</project>

View File

@@ -2,9 +2,7 @@ import http from 'k6/http';
import { sleep } from 'k6';
const INTERNAL_BASE_URL = 'http://localhost:16790/api/v1';
const EXTERNAL_BASE_URL = __ENV.SEPARATE_INTERNAL_EXTERNAL_PORTS ? 'http://localhost:16789/api/v1' : INTERNAL_BASE_URL;
const BASE_URL = 'http://localhost:16789/api/v1';
export default function () {
const endpoints = [
@@ -14,19 +12,13 @@ export default function () {
'getToplogy',
'currentModel',
];
const internalRequests = endpoints.map(endpoint => {
const requests = endpoints.map(endpoint => {
return {
method: 'GET',
url: `${INTERNAL_BASE_URL}/${endpoint}`,
};
});
const externalRequests = endpoints.map(endpoint => {
return {
method: 'GET',
url: `${EXTERNAL_BASE_URL}/${endpoint}`,
url: `${BASE_URL}/${endpoint}`,
};
});
let responses = http.batch([...internalRequests, ...externalRequests]);
let responses = http.batch(requests);
sleep(0.1);
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.lang.reflect.Field;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.io.File;
import java.io.FileNotFoundException;
@@ -47,17 +47,17 @@ public class DeviceDataManager {
private final ReadWriteLock topologyLock = new ReentrantReadWriteLock();
/** Lock on {@link #deviceLayeredConfig}. */
public final ReadWriteLock deviceLayeredConfigLock =
private final ReadWriteLock deviceLayeredConfigLock =
new ReentrantReadWriteLock();
/** The current device topology. */
public DeviceTopology topology;
private DeviceTopology topology;
/** The current layered device config. */
public DeviceLayeredConfig deviceLayeredConfig;
private DeviceLayeredConfig deviceLayeredConfig;
/** The cached device configs (map of serial number to computed config). */
public Map<String, DeviceConfig> cachedDeviceConfigs =
private Map<String, DeviceConfig> cachedDeviceConfigs =
new ConcurrentHashMap<>();
/** Empty constructor without backing files (ex. for unit tests). */

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.Map;
import java.util.TreeMap;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.Set;
import java.util.TreeMap;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.io.File;
import java.io.FileWriter;
@@ -18,10 +18,11 @@ import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -154,7 +155,8 @@ public class Launcher implements Callable<Integer> {
: DEFAULT_DEVICE_LAYERED_CONFIG_FILE
);
String serviceKey = Utils.generateServiceKey(config.serviceConfig);
String serviceKey =
UCentralUtils.generateServiceKey(config.serviceConfig);
// Instantiate clients
UCentralClient.verifySsl(config.uCentralConfig.verifySsl);
@@ -164,9 +166,7 @@ public class Launcher implements Callable<Integer> {
config.uCentralConfig.uCentralSecPublicEndpoint,
config.uCentralConfig.username,
config.uCentralConfig.password,
config.uCentralConfig.uCentralSocketParams.connectTimeoutMs,
config.uCentralConfig.uCentralSocketParams.socketTimeoutMs,
config.uCentralConfig.uCentralSocketParams.wifiScanTimeoutMs
config.uCentralConfig.uCentralSocketParams
);
UCentralKafkaConsumer consumer;
UCentralKafkaProducer producer;
@@ -265,7 +265,7 @@ public class Launcher implements Callable<Integer> {
.setPrettyPrinting()
.serializeNulls() // for here only!!
.create();
System.out.println(gson.toJson(DeviceConfig.createDefault()));
logger.info(gson.toJson(DeviceConfig.createDefault()));
return 0;
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.Arrays;
import java.util.List;
@@ -18,18 +18,18 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.kafka.KafkaRunner;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
import com.facebook.openwifi.rrm.modules.ApiServer;
import com.facebook.openwifi.rrm.modules.ConfigManager;
import com.facebook.openwifi.rrm.modules.DataCollector;
import com.facebook.openwifi.rrm.modules.Modeler;
import com.facebook.openwifi.rrm.modules.ProvMonitor;
import com.facebook.openwifi.rrm.modules.RRMScheduler;
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.modules.ApiServer;
import com.facebook.openwifirrm.modules.ConfigManager;
import com.facebook.openwifirrm.modules.DataCollector;
import com.facebook.openwifirrm.modules.Modeler;
import com.facebook.openwifirrm.modules.ProvMonitor;
import com.facebook.openwifirrm.modules.RRMScheduler;
import com.facebook.openwifirrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.ucentral.KafkaRunner;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
/**
* RRM service runner.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.HashMap;
import java.util.Map;
@@ -14,17 +14,17 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.rrm.modules.ConfigManager;
import com.facebook.openwifi.rrm.modules.Modeler;
import com.facebook.openwifi.rrm.optimizers.channel.ChannelOptimizer;
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
import com.facebook.openwifi.rrm.optimizers.tpc.TPC;
import com.facebook.openwifirrm.modules.ConfigManager;
import com.facebook.openwifirrm.modules.Modeler;
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
import com.facebook.openwifirrm.optimizers.tpc.TPC;
/**
* RRM algorithm model and utility methods.
@@ -143,9 +143,6 @@ public class RRMAlgorithm {
* @param dryRun if set, do not apply changes
* @param allowDefaultMode if false, "mode" argument must be present and
* 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
* failure, any others upon success)
@@ -156,8 +153,7 @@ public class RRMAlgorithm {
Modeler modeler,
String zone,
boolean dryRun,
boolean allowDefaultMode,
boolean updateImmediately
boolean allowDefaultMode
) {
AlgorithmResult result = new AlgorithmResult();
if (name == null || args == null) {
@@ -216,14 +212,11 @@ public class RRMAlgorithm {
}
result.channelMap = optimizer.computeChannelMap();
if (!dryRun) {
optimizer.updateDeviceApConfig(
optimizer.applyConfig(
deviceDataManager,
configManager,
result.channelMap
);
if (updateImmediately) {
configManager.queueZoneAndWakeUp(zone);
}
}
} else if (
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
@@ -277,14 +270,11 @@ public class RRMAlgorithm {
}
result.txPowerMap = optimizer.computeTxPowerMap();
if (!dryRun) {
optimizer.updateDeviceApConfig(
optimizer.applyConfig(
deviceDataManager,
configManager,
result.txPowerMap
);
if (updateImmediately) {
configManager.queueZoneAndWakeUp(zone);
}
}
} else {
result.error = String.format("Unknown algorithm: '%s'", name);

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.Map;
@@ -34,7 +34,7 @@ public class RRMConfig {
* Private endpoint for the RRM service
* ({@code SERVICECONFIG_PRIVATEENDPOINT})
*/
public String privateEndpoint = "http://owrrm.wlan.local:16790"; // see ApiServerParams.internalHttpPort
public String privateEndpoint = "http://owrrm.wlan.local:16789"; // see ApiServerParams.httpPort
/**
* Public endpoint for the RRM service
@@ -60,7 +60,7 @@ public class RRMConfig {
* ({@code SERVICECONFIG_VENDORREFERENCEURL})
*/
public String vendorReferenceUrl =
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/owrrm/ALGORITHMS.md";
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/ALGORITHMS.md";
}
/** Service configuration. */
@@ -232,7 +232,7 @@ public class RRMConfig {
* The main logic loop interval (i.e. sleep time), in ms
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
*/
public int updateIntervalMs = 30000; // 30sec
public int updateIntervalMs = 5000;
/**
* The expected device statistics interval, in seconds (or -1 to
@@ -246,13 +246,13 @@ public class RRMConfig {
* automatic scans)
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
*/
public int wifiScanIntervalSec = 900; // 15min
public int wifiScanIntervalSec = 900;
/**
* The capabilities request interval (per device), in seconds
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
*/
public int capabilitiesIntervalSec = 3600; // 1hr
public int capabilitiesIntervalSec = 3600;
/**
* Number of executor threads for async tasks (ex. wifi scans)
@@ -273,7 +273,7 @@ public class RRMConfig {
* The main logic loop interval (i.e. sleep time), in ms
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
*/
public int updateIntervalMs = 120000; // 2min
public int updateIntervalMs = 60000;
/**
* Enable pushing device config changes?
@@ -309,12 +309,6 @@ public class RRMConfig {
* ({@code MODELERPARAMS_WIFISCANBUFFERSIZE})
*/
public int wifiScanBufferSize = 10;
/**
* Maximum rounds of States to store per device
* ({@code MODELERPARAMS_STATEBUFFERSIZE})
*/
public int stateBufferSize = 10;
}
/** Modeler parameters. */
@@ -325,16 +319,10 @@ public class RRMConfig {
*/
public class ApiServerParams {
/**
* The HTTP port to listen on for internal traffic, or -1 to disable
* ({@code APISERVERPARAMS_INTERNALHTTPPORT})
* The HTTP port to listen on, or -1 to disable
* ({@code APISERVERPARAMS_HTTPPORT})
*/
public int internalHttpPort = 16790;
/**
* The HTTP port to listen on for external traffic, or -1 to disable
* ({@code APISERVERPARAMS_EXTERNALHTTPPORT})
*/
public int externalHttpPort = 16789;
public int httpPort = 16789;
/**
* Comma-separated list of all allowed CORS domains (exact match
@@ -375,7 +363,7 @@ public class RRMConfig {
* Sync interval, in ms, for owprov venue information etc.
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
*/
public int syncIntervalMs = 300000; // 5min
public int syncIntervalMs = 300000;
}
/** ProvMonitor parameters. */
@@ -544,16 +532,10 @@ public class RRMConfig {
if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) {
modelerParams.wifiScanBufferSize = Integer.parseInt(v);
}
if ((v = env.get("MODELERPARAMS_STATEBUFFERSIZE")) != null) {
modelerParams.stateBufferSize = Integer.parseInt(v);
}
ModuleConfig.ApiServerParams apiServerParams =
config.moduleConfig.apiServerParams;
if ((v = env.get("APISERVERPARAMS_INTERNALHTTPPORT")) != null) {
apiServerParams.internalHttpPort = Integer.parseInt(v);
}
if ((v = env.get("APISERVERPARAMS_EXTERNALHTTPPORT")) != null) {
apiServerParams.externalHttpPort = Integer.parseInt(v);
if ((v = env.get("APISERVERPARAMS_HTTPPORT")) != null) {
apiServerParams.httpPort = Integer.parseInt(v);
}
if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) {
apiServerParams.corsDomainList = v;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.util.List;
@@ -19,9 +19,9 @@ public class RRMSchedule {
*
* This field expects a cron-like format as defined by the Quartz Job
* Scheduler (CronTrigger):
* https://www.quartz-scheduler.org/documentation/quartz-2.4.0/tutorials/crontrigger.html
* https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
*/
public List<String> crons;
public String cron;
/**
* The list of RRM algorithms to run.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import java.io.File;
import java.io.FileNotFoundException;
@@ -16,8 +16,6 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
@@ -25,8 +23,6 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -35,8 +31,6 @@ import com.google.gson.GsonBuilder;
* Generic utility methods.
*/
public class Utils {
private static final Logger logger = LoggerFactory.getLogger(Utils.class);
/** Hex value array for use in {@link #longToMac(long)}. */
private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray();
@@ -199,19 +193,4 @@ public class Utils {
public static <T> T deepCopy(T obj, Class<T> classOfT) {
return gson.fromJson(gson.toJson(obj), classOfT);
}
/** Generate the RRM service key. */
public static String generateServiceKey(
RRMConfig.ServiceConfig serviceConfig
) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(serviceConfig.publicEndpoint.getBytes());
sha256.update(serviceConfig.privateEndpoint.getBytes());
return Utils.bytesToHex(sha256.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("Unable to generate service key", e);
return "";
}
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm;
package com.facebook.openwifirrm;
import picocli.CommandLine.IVersionProvider;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.aggregators;
package com.facebook.openwifirrm.aggregators;
/**
* Aggregates added values into one "aggregate" measure.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.aggregators;
package com.facebook.openwifirrm.aggregators;
/**
* Tracks the mean of all added values. If no values are added, the mean is 0.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.net.InetAddress;
import java.net.URI;
@@ -35,33 +35,31 @@ import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Algorithm;
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Provider;
import com.facebook.openwifi.rrm.CustomJettyServerFactory;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.DeviceLayeredConfig;
import com.facebook.openwifi.rrm.DeviceTopology;
import com.facebook.openwifi.rrm.RRMAlgorithm;
import com.facebook.openwifi.rrm.Utils;
import com.facebook.openwifi.rrm.VersionProvider;
import com.facebook.openwifi.rrm.RRMConfig.ServiceConfig;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ApiServerParams;
import com.facebook.openwifi.rrm.Utils.LruCache;
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.DeviceLayeredConfig;
import com.facebook.openwifirrm.DeviceTopology;
import com.facebook.openwifirrm.RRMAlgorithm;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ApiServerParams;
import com.facebook.openwifirrm.RRMConfig.ServiceConfig;
import com.facebook.openwifirrm.Utils.LruCache;
import com.facebook.openwifirrm.VersionProvider;
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Algorithm;
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Provider;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.openjdk.jol.info.GraphLayout;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.jaxrs2.Reader;
@@ -83,9 +81,7 @@ import io.swagger.v3.oas.models.OpenAPI;
import spark.Request;
import spark.Response;
import spark.Route;
import spark.Service;
import spark.embeddedserver.EmbeddedServers;
import spark.embeddedserver.jetty.EmbeddedJettyFactory;
import spark.Spark;
/**
* HTTP API server.
@@ -114,27 +110,6 @@ public class ApiServer implements Runnable {
private static final Logger logger =
LoggerFactory.getLogger(ApiServer.class);
/**
* This is the identifier for the server factory that Spark should use. This
* particular identifier points to the custom factory that we register to
* enable running multiple ports on one service.
*
* @see #run()
*/
private static final String SPARK_EMBEDDED_SERVER_IDENTIFIER =
ApiServer.class.getName();
/**
* The Spark service instance. Normally, you would use the static methods on
* Spark, but since we need to spin up multiple instances of Spark for testing,
* we choose to go with instantiating the service ourselves. There is really no
* difference except with the static method, Spark calls ignite and holds a
* singleton instance for us.
*
* @see Spark
*/
private final Service service;
/** The module parameters. */
private final ApiServerParams params;
@@ -189,10 +164,9 @@ public class ApiServer implements Runnable {
UCentralClient client,
RRMScheduler scheduler
) {
this.service = Service.ignite();
this.params = params;
this.serviceConfig = serviceConfig;
this.serviceKey = Utils.generateServiceKey(serviceConfig);
this.serviceKey = UCentralUtils.generateServiceKey(serviceConfig);
this.deviceDataManager = deviceDataManager;
this.configManager = configManager;
this.modeler = modeler;
@@ -220,117 +194,64 @@ public class ApiServer implements Runnable {
return ret;
}
/**
* Block until initialization finishes. Just calls the method on the
* underlying service.
*/
public void awaitInitialization() {
service.awaitInitialization();
}
@Override
public void run() {
this.startTimeMs = System.currentTimeMillis();
if (params.internalHttpPort == -1 && params.externalHttpPort == -1) {
if (params.httpPort == -1) {
logger.info("API server is disabled.");
return;
} else if (params.internalHttpPort == -1) {
logger.info("Internal API server is disabled");
} else if (params.externalHttpPort == -1) {
logger.info("External API server is disabled");
}
if (params.internalHttpPort == params.externalHttpPort) {
logger.error(
"Internal and external port cannot be the same - not starting API server"
);
return;
}
EmbeddedServers.add(
SPARK_EMBEDDED_SERVER_IDENTIFIER,
new EmbeddedJettyFactory(
new CustomJettyServerFactory(
params.internalHttpPort,
params.externalHttpPort
)
)
);
// use the embedded server factory added above, this is required so that we
// don't mess up the default factory which can and will be used for
// additional Spark services in testing
service.embeddedServerIdentifier(SPARK_EMBEDDED_SERVER_IDENTIFIER);
// Usually you would call this with an actual port and Spark would spin up a
// port on it. However, since we're putting our own connectors in so that we
// can use two ports and Spark has logic to use connectors that already exist
// so it doesn't matter what port we pass in here as long as it's not one of
// the actual ports we're using (Spark has some weird logic where it still
// tries to bind to the port).
// @see EmbeddedJettyServer
service.port(0);
Spark.port(params.httpPort);
// Configure API docs hosting
service.staticFiles.location("/public");
service.get("/openapi.yaml", this::getOpenApiYaml);
service.get("/openapi.json", this::getOpenApiJson);
Spark.staticFiles.location("/public");
Spark.get("/openapi.yaml", this::getOpenApiYaml);
Spark.get("/openapi.json", this::getOpenApiJson);
// Install routes
service.before(this::beforeFilter);
service.after(this::afterFilter);
service.options("/*", this::options);
service.get("/api/v1/system", new SystemEndpoint());
service.post("/api/v1/system", new SetSystemEndpoint());
service.get("/api/v1/provider", new ProviderEndpoint());
service.get("/api/v1/algorithms", new AlgorithmsEndpoint());
service.put("/api/v1/runRRM", new RunRRMEndpoint());
service.get("/api/v1/getTopology", new GetTopologyEndpoint());
service.post("/api/v1/setTopology", new SetTopologyEndpoint());
service.get(
Spark.before(this::beforeFilter);
Spark.after(this::afterFilter);
Spark.options("/*", this::options);
Spark.get("/api/v1/system", new SystemEndpoint());
Spark.post("/api/v1/system", new SetSystemEndpoint());
Spark.get("/api/v1/provider", new ProviderEndpoint());
Spark.get("/api/v1/algorithms", new AlgorithmsEndpoint());
Spark.put("/api/v1/runRRM", new RunRRMEndpoint());
Spark.get("/api/v1/getTopology", new GetTopologyEndpoint());
Spark.post("/api/v1/setTopology", new SetTopologyEndpoint());
Spark.get(
"/api/v1/getDeviceLayeredConfig",
new GetDeviceLayeredConfigEndpoint()
);
service.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
service.post(
Spark.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
Spark.post(
"/api/v1/setDeviceNetworkConfig",
new SetDeviceNetworkConfigEndpoint()
);
service.post(
Spark.post(
"/api/v1/setDeviceZoneConfig",
new SetDeviceZoneConfigEndpoint()
);
service.post(
Spark.post(
"/api/v1/setDeviceApConfig",
new SetDeviceApConfigEndpoint()
);
service.post(
Spark.post(
"/api/v1/modifyDeviceApConfig",
new ModifyDeviceApConfigEndpoint()
);
service.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
service.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
service.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
service.get("/api/v1/memory", new MemoryEndpoint(this));
Spark.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
Spark.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
Spark.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
logger.info(
"API server listening for HTTP internal on port {} and external on port {}",
params.internalHttpPort,
params.externalHttpPort
);
logger.info("API server listening on HTTP port {}", params.httpPort);
}
/** Stop the server. */
public void shutdown() {
service.stop();
}
/**
* Block until stop finishes. Just calls the method on the underlying service.
*/
public void awaitStop() {
service.awaitStop();
Spark.stop();
}
/** Reconstructs a URL. */
@@ -348,17 +269,15 @@ public class ApiServer implements Runnable {
* HTTP 403 response and return false.
*/
private boolean performOpenWifiAuth(Request request, Response response) {
int port = request.port();
boolean internal = port > 0 && port == params.internalHttpPort;
if (internal) {
String internalName = request.headers("X-INTERNAL-NAME");
if (internalName != null) {
// Internal request, validate "X-API-KEY"
String apiKey = request.headers("X-API-KEY");
if (apiKey != null && apiKey.equals(serviceKey)) {
// auth success
return true;
}
// TODO check if request came from internal endpoint
boolean internal = true;
String internalName = request.headers("X-INTERNAL-NAME");
if (internal && internalName != null) {
// Internal request, validate "X-API-KEY"
String apiKey = request.headers("X-API-KEY");
if (apiKey != null && apiKey.equals(serviceKey)) {
// auth success
return true;
}
} else {
// External request, validate token:
@@ -378,18 +297,17 @@ public class ApiServer implements Runnable {
}
// auth failure
service.halt(403, "Forbidden");
Spark.halt(403, "Forbidden");
return false;
}
/**
* Validate an OpenWiFi token (external), caching successful lookups. This will
* validate a USER token - subscriber token won't work and will fail (plus only
* users should be dealing with RRM).
* Validate an OpenWiFi token (external), caching successful lookups.
* @return true if token is valid
*/
private boolean validateOpenWifiToken(String token) {
// The below only checks /api/v1/validateToken and caches it as necessary.
// TODO - /api/v1/validateSubToken still has to be implemented.
Long expiry = tokenCache.get(token);
if (expiry == null) {
TokenValidationResult result = client.validateToken(token);
@@ -406,11 +324,10 @@ public class ApiServer implements Runnable {
private void beforeFilter(Request request, Response response) {
// Log requests
logger.debug(
"[{}] {} {} on port {}",
"[{}] {} {}",
request.ip(),
request.requestMethod(),
getFullUrl(request.pathInfo(), request.queryString()),
request.port()
getFullUrl(request.pathInfo(), request.queryString())
);
// Remove "Server: Jetty" header
@@ -794,8 +711,7 @@ public class ApiServer implements Runnable {
modeler,
venue,
mock,
true, /* allowDefaultMode */
true /* updateImmediately */
true /* allowDefaultMode */
);
if (result.error != null) {
response.status(400);
@@ -1001,7 +917,7 @@ public class ApiServer implements Runnable {
DeviceConfig networkConfig =
gson.fromJson(request.body(), DeviceConfig.class);
deviceDataManager.setDeviceNetworkConfig(networkConfig);
configManager.queueAllZonesAndWakeUp();
configManager.wakeUp();
// Revalidate data model
modeler.revalidate();
@@ -1065,7 +981,7 @@ public class ApiServer implements Runnable {
DeviceConfig zoneConfig =
gson.fromJson(request.body(), DeviceConfig.class);
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
configManager.queueZoneAndWakeUp(zone);
configManager.wakeUp();
// Revalidate data model
modeler.revalidate();
@@ -1128,10 +1044,7 @@ public class ApiServer implements Runnable {
DeviceConfig apConfig =
gson.fromJson(request.body(), DeviceConfig.class);
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
// TODO enable updates to device(s), not just the entire zone
final String zone =
deviceDataManager.getDeviceZone(serialNumber);
configManager.queueZoneAndWakeUp(zone);
configManager.wakeUp();
// Revalidate data model
modeler.revalidate();
@@ -1204,10 +1117,7 @@ public class ApiServer implements Runnable {
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
.apply(apConfig);
});
final String zone =
deviceDataManager.getDeviceZone(serialNumber);
// TODO enable updates to device(s), not just the entire zone
configManager.queueZoneAndWakeUp(zone);
configManager.wakeUp();
// Revalidate data model
modeler.revalidate();
@@ -1350,8 +1260,7 @@ public class ApiServer implements Runnable {
modeler,
zone,
dryRun,
false, /* allowDefaultMode */
true /* updateImmediately */
false /* allowDefaultMode */
);
if (result.error != null) {
response.status(400);
@@ -1362,91 +1271,6 @@ public class ApiServer implements Runnable {
}
}
@Path("/api/v1/memory")
public class MemoryEndpoint implements Route {
private final ApiServer apiServer;
MemoryEndpoint(ApiServer server) {
this.apiServer = server;
}
@Override
public String handle(Request request, Response response) {
String type = request.queryParamOrDefault("type", "");
String view = request.queryParamOrDefault("view", "footprint");
java.util.function.Function<GraphLayout, String> fn = (GraphLayout graph) -> {
return view.equals("footprint") ? graph.toFootprint() : graph.toPrintable();
};
String result;
switch (type) {
case "modeler.dataModel":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel));
break;
case "modeler.dataModel.latestWifiScans":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestWifiScans));
break;
case "modeler.dataModel.latestStates":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestStates));
break;
case "modeler.dataModel.latestDeviceStatusRadios":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestDeviceStatusRadios));
break;
case "modeler.deviceDataManager":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager));
break;
case "modeler.deviceDataManager.topology":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.topology));
break;
case "modeler.deviceDataManager.deviceLayeredConfig":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.deviceLayeredConfig));
break;
case "modeler.deviceDataManager.cachedDeviceConfigs":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.cachedDeviceConfigs));
break;
case "modeler.dataModel.latestDeviceCapabilities":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestDeviceCapabilities));
case "modeler":
result = fn.apply(GraphLayout.parseInstance(apiServer.modeler));
break;
case "configManager.deviceDataMap":
result = fn.apply(GraphLayout.parseInstance(apiServer.configManager.deviceDataMap));
break;
case "configManager":
result = fn.apply(GraphLayout.parseInstance(apiServer.configManager));
break;
case "scheduler":
result = fn.apply(GraphLayout.parseInstance(apiServer.scheduler));
break;
case "deviceDataManager":
result = fn.apply(GraphLayout.parseInstance(apiServer.deviceDataManager));
break;
case "":
default:
result = GraphLayout.parseInstance(apiServer).toFootprint();
break;
}
logger.info("MEMORY RESPONSE: \n{}", result);
return result;
}
}
@Path("/api/v1/optimizeTxPower")
public class OptimizeTxPowerEndpoint implements Route {
// Hack for use in @ApiResponse -> @Content -> @Schema
@@ -1547,8 +1371,7 @@ public class ApiServer implements Runnable {
modeler,
zone,
dryRun,
false, /* allowDefaultMode */
true /* updateImmediately */
false /* allowDefaultMode */
);
if (result.error != null) {
response.status(400);

View File

@@ -6,28 +6,25 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ConfigManagerParams;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ConfigManagerParams;
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
/**
* Device configuration manager module.
@@ -46,7 +43,7 @@ public class ConfigManager implements Runnable {
private final UCentralClient client;
/** Runtime per-device data. */
public class DeviceData {
private class DeviceData {
/** Last received device config. */
public UCentralApConfiguration config;
@@ -55,7 +52,7 @@ public class ConfigManager implements Runnable {
}
/** Map from device serial number to runtime data. */
public Map<String, DeviceData> deviceDataMap = new TreeMap<>();
private Map<String, DeviceData> deviceDataMap = new TreeMap<>();
/** The main thread reference (i.e. where {@link #run()} is invoked). */
private Thread mainThread;
@@ -66,11 +63,8 @@ public class ConfigManager implements Runnable {
/** Is the main thread sleeping? */
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
/**
* Thread-safe set of zones for which manual config updates have been
* requested.
*/
private Set<String> zonesToUpdate = ConcurrentHashMap.newKeySet();
/** Was a manual config update requested? */
private final AtomicBoolean eventFlag = new AtomicBoolean(false);
/** Config listener interface. */
public interface ConfigListener {
@@ -186,10 +180,7 @@ public class ConfigManager implements Runnable {
List<String> devicesNeedingUpdate = new ArrayList<>();
final long CONFIG_DEBOUNCE_INTERVAL_NS =
params.configDebounceIntervalSec * 1_000_000_000L;
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);
final boolean isEvent = eventFlag.getAndSet(false);
for (DeviceWithStatus device : devices) {
// Update config structure
DeviceData data = deviceDataMap.computeIfAbsent(
@@ -210,13 +201,11 @@ public class ConfigManager implements Runnable {
for (ConfigListener listener : configListeners.values()) {
listener.receiveDeviceConfig(device.serialNumber, data.config);
}
// Check if there are requested updates for this zone
String deviceZone =
deviceDataManager.getDeviceZone(device.serialNumber);
boolean isEvent = zonesToUpdateCopy.contains(deviceZone);
// Check event flag
if (params.configOnEventOnly && !isEvent) {
logger.debug(
"Skipping config for {} (zone not marked for updates)",
"Skipping config for {} (event flag not set)",
device.serialNumber
);
continue;
@@ -262,16 +251,15 @@ public class ConfigManager implements Runnable {
}
}
final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty();
// Send config changes to devices
if (!params.configEnabled) {
logger.trace("Config changes are disabled.");
} else if (devicesNeedingUpdate.isEmpty()) {
logger.debug("No device configs to send.");
} else if (params.configOnEventOnly && !shouldUpdate) {
} else if (params.configOnEventOnly && !isEvent) {
// shouldn't happen
logger.error(
"ERROR!! {} device(s) queued for config update, but no zones queued for update.",
"ERROR!! {} device(s) queued for config update, but event flag not set",
devicesNeedingUpdate.size()
);
} else {
@@ -376,38 +364,9 @@ public class ConfigManager implements Runnable {
return (configListeners.remove(id) != null);
}
/**
* Mark the zone to be updated, then interrupt the main thread to possibly
* 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() {
/** Interrupt the main thread, possibly triggering an update immediately. */
public void wakeUp() {
eventFlag.set(true);
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
wakeupFlag.set(true);
mainThread.interrupt();

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -23,22 +23,22 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.Utils;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.DataCollectorParams;
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
import com.facebook.openwifi.rrm.mysql.StateRecord;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.DataCollectorParams;
import com.facebook.openwifirrm.Utils;
import com.facebook.openwifirrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.mysql.StateRecord;
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.util.LinkedList;
import java.util.List;
@@ -20,21 +20,21 @@ import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ModelerParams;
import com.facebook.openwifi.rrm.Utils;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ModelerParams;
import com.facebook.openwifirrm.Utils;
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
import com.facebook.openwifirrm.ucentral.models.State;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@@ -50,7 +50,7 @@ public class Modeler implements Runnable {
private final ModelerParams params;
/** The device data manager. */
public final DeviceDataManager deviceDataManager;
private final DeviceDataManager deviceDataManager;
/** The uCentral client instance. */
private final UCentralClient client;
@@ -74,7 +74,7 @@ public class Modeler implements Runnable {
}
/** The blocking data queue. */
public final BlockingQueue<InputData> dataQueue =
private final BlockingQueue<InputData> dataQueue =
new LinkedBlockingQueue<>();
/** Data model representation. */
@@ -93,9 +93,8 @@ public class Modeler implements Runnable {
public Map<String, List<List<WifiScanEntry>>> latestWifiScans =
new ConcurrentHashMap<>();
/** List of latest states per device. */
public Map<String, List<State>> latestStates =
new ConcurrentHashMap<>();
/** List of latest state per device. */
public Map<String, State> latestState = new ConcurrentHashMap<>();
/** List of radio info per device. */
public Map<String, JsonArray> latestDeviceStatusRadios =
@@ -268,10 +267,7 @@ public class Modeler implements Runnable {
if (state != null) {
try {
State stateModel = gson.fromJson(state, State.class);
dataModel.latestStates.computeIfAbsent(
device.serialNumber,
k -> new LinkedList<>()
).add(stateModel);
dataModel.latestState.put(device.serialNumber, stateModel);
logger.debug(
"Device {}: added initial state from uCentralGw",
device.serialNumber
@@ -303,17 +299,8 @@ public class Modeler implements Runnable {
if (state != null) {
try {
State stateModel = gson.fromJson(state, State.class);
List<State> latestStatesList = dataModel.latestStates
.computeIfAbsent(
record.serialNumber,
k -> new LinkedList<>()
);
while (
latestStatesList.size() >= params.stateBufferSize
) {
latestStatesList.remove(0);
}
latestStatesList.add(stateModel);
dataModel.latestState
.put(record.serialNumber, stateModel);
stateUpdates.add(record.serialNumber);
} catch (JsonSyntaxException e) {
logger.error(
@@ -436,7 +423,7 @@ public class Modeler implements Runnable {
logger.debug("Removed some wifi scan entries from data model");
}
if (
dataModel.latestStates.entrySet()
dataModel.latestState.entrySet()
.removeIf(e -> !isRRMEnabled(e.getKey()))
) {
logger.debug("Removed some state entries from data model");

View File

@@ -6,33 +6,24 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.AggregatedState;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface;
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID;
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association;
import com.facebook.openwifi.rrm.aggregators.Aggregator;
import com.facebook.openwifi.rrm.aggregators.MeanAggregator;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.facebook.openwifirrm.aggregators.Aggregator;
import com.facebook.openwifirrm.aggregators.MeanAggregator;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
/**
* Modeler utilities.
@@ -248,9 +239,9 @@ public class ModelerUtils {
return Objects.equals(entry1.bssid, entry2.bssid) &&
entry1.frequency == entry2.frequency &&
entry1.channel == entry2.channel &&
HTOperation
HTOperationElement
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
VHTOperation
VHTOperationElement
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
}
@@ -300,7 +291,7 @@ public class ModelerUtils {
/**
* Compute aggregated wifiscans using a given reference time.
*
* @see #getAggregatedWifiScans(com.facebook.openwifi.rrm.modules.Modeler.DataModel,
* @see #getAggregatedWifiScans(com.facebook.openwifirrm.modules.Modeler.DataModel,
* long, Aggregator)
*/
public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans(
@@ -390,196 +381,4 @@ public class ModelerUtils {
}
return aggregatedWifiScans;
}
/**
* This method converts the input State info to an AggregatedState
* and adds it to the bssidToAggregatedStates map. If the bssid/station
* of the input State does not exist in the map, create a new
* AggregatedState list. If the bssid/station of the input State exists,
* then convert State to AggregatedState and check if there exits an
* AggregatedState of the same radio. If there does, append the value
* of aggregation field to the existing AggregatedState, if not, create
* a new AggregatedState and add it to the list.
*
* @param bssidToAggregatedStates map from bssid/station to a list of AggregatedState
* @param state the state that is to be added
*/
static void addStateToAggregation(
Map<String, List<AggregatedState>> bssidToAggregatedStates,
State state
) {
for (Interface stateInterface : state.interfaces) {
if (stateInterface.ssids == null) {
continue;
}
for (SSID ssid : stateInterface.ssids) {
Map<String, Integer> radioInfo = new HashMap<>();
radioInfo.put("channel", ssid.radio.get("channel").getAsInt());
radioInfo.put(
"channel_width",
ssid.radio.get("channel_width").getAsInt()
);
radioInfo
.put("tx_power", ssid.radio.get("tx_power").getAsInt());
for (Association association : ssid.associations) {
if (association == null) {
continue;
}
String key = getBssidStationKeyPair(
association.bssid,
association.station
);
List<AggregatedState> aggregatedStates =
bssidToAggregatedStates
.computeIfAbsent(key, k -> new ArrayList<>());
AggregatedState aggState =
new AggregatedState(association, radioInfo);
/**
* Indicate if the aggState can be merged into some old AggregatedState.
* If true, it will be merged by appending its mcs/rssi field to the old one.
* If false, it will be added to the list aggregatedStates.
*/
boolean canBeMergedToOldAggregatedState = false;
for (
AggregatedState oldAggregatedState : aggregatedStates
) {
if (oldAggregatedState.add(aggState)) {
canBeMergedToOldAggregatedState = true;
break;
}
}
if (!canBeMergedToOldAggregatedState) {
aggregatedStates.add(aggState);
}
bssidToAggregatedStates.put(key, aggregatedStates);
}
}
}
}
/**
* This method aggregates States by bssid/station key pair and radio info.
* if two States of the same bssid/station match in channel, channel width and tx_power
* need to be aggregated to one {@code AggregatedState}. Currently only mcs and
* rssi fields are being aggregated. They are of {@code List<Integer>} type in AggregatedState,
* which list all the values over the time.
*
* @param dataModel the data model which includes the latest recorded States
* @param obsoletionPeriodMs the maximum amount of time (in milliseconds) it
* is worth aggregating over, starting from the
* most recent States and working backwards in time.
* A State exactly {@code obsoletionPeriodMs} ms earlier
* than the most recent State is considered non-obsolete
* (i.e., the "non-obsolete" window is inclusive).
* Must be non-negative.
* @param refTimeMs the reference time were passed to make testing easier
* @return map from serial number to a map from bssid_station String pair to a list of AggregatedState
*/
public static Map<String, Map<String, List<AggregatedState>>> getAggregatedStates(
Modeler.DataModel dataModel,
long obsoletionPeriodMs,
long refTimeMs
) {
if (obsoletionPeriodMs < 0) {
throw new IllegalArgumentException(
"obsoletionPeriodMs must be non-negative."
);
}
Map<String, Map<String, List<AggregatedState>>> aggregatedStates =
new HashMap<>();
for (
Map.Entry<String, List<State>> deviceToStateList : dataModel.latestStates
.entrySet()
) {
String serialNumber = deviceToStateList.getKey();
List<State> states = deviceToStateList.getValue();
if (states.isEmpty()) {
continue;
}
/**
* Sort in reverse chronological order. Sorting is done just in case the
* States in the original list are not chronological already - although
* they are inserted chronologically, perhaps latency, synchronization, etc.
*/
states.sort(
(state1, state2) -> -Long.compare(state1.unit.localtime, state2.unit.localtime)
);
Map<String, List<AggregatedState>> bssidToAggregatedStates =
aggregatedStates
.computeIfAbsent(serialNumber, k -> new HashMap<>());
for (State state : states) {
if (refTimeMs - state.unit.localtime > obsoletionPeriodMs) {
// discard obsolete entries
break;
}
addStateToAggregation(bssidToAggregatedStates, state);
}
}
return aggregatedStates;
}
/**
* This method gets the most recent State from latestStates per device.
*
* @param latestStates list of latest States per device
* @return map from device String to latest State
*/
public static Map<String, State> getLatestState(
Map<String, List<State>> latestStates
) {
Map<String, State> latestState = new ConcurrentHashMap<>();
for (
Map.Entry<String, List<State>> stateEntry : latestStates.entrySet()
) {
String key = stateEntry.getKey();
List<State> value = stateEntry.getValue();
if (value.isEmpty()) {
latestState.put(key, null);
} else {
latestState.put(key, value.get(value.size() - 1));
}
}
return latestState;
}
/** Create a key pair consisted of bssid and station string */
public static String getBssidStationKeyPair(String bssid, String station) {
return String.format(
"bssid: %s, station: %s",
bssid,
station
);
}
/** Return the radio's band, or null if band cannot be found */
public static String getBand(
State.Radio radio,
JsonObject deviceCapability
) {
if (radio.phy == null) {
return null;
}
JsonElement radioCapabilityElement = deviceCapability.get(radio.phy);
if (radioCapabilityElement == null) {
return null;
}
JsonObject radioCapability = radioCapabilityElement.getAsJsonObject();
JsonElement bandsElement = radioCapability.get("band");
if (bandsElement == null) {
return null;
}
JsonArray bands = bandsElement.getAsJsonArray();
if (bands.isEmpty()) {
return null;
}
return bands.get(0).getAsString();
}
}

View File

@@ -6,9 +6,8 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -18,19 +17,19 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTag;
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList;
import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails;
import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList;
import com.facebook.openwifi.cloudsdk.models.prov.Venue;
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.DeviceTopology;
import com.facebook.openwifi.rrm.RRMAlgorithm;
import com.facebook.openwifi.rrm.RRMSchedule;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ProvMonitorParams;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.DeviceTopology;
import com.facebook.openwifirrm.RRMAlgorithm;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ProvMonitorParams;
import com.facebook.openwifirrm.RRMSchedule;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTag;
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails;
import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList;
import com.facebook.openwifirrm.ucentral.prov.models.Venue;
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
/**
* owprov monitor module.
@@ -160,21 +159,12 @@ public class ProvMonitor implements Runnable {
return null;
}
String[] crons = RRMScheduler
RRMSchedule schedule = new RRMSchedule();
schedule.cron = RRMScheduler
.parseIntoQuartzCron(details.rrm.schedule);
if (crons == null || crons.length == 0) {
if (schedule.cron == null || schedule.cron.isEmpty()) {
return null;
}
// if ANY crons are invalid throw it out since it doesn't make sense to
// schedule partial jobs
for (String cron : crons) {
if (cron == null || cron.isEmpty()) {
return null;
}
}
RRMSchedule schedule = new RRMSchedule();
schedule.crons = Arrays.asList(crons);
if (details.rrm.algorithms != null) {
schedule.algorithms =
@@ -185,7 +175,6 @@ public class ProvMonitor implements Runnable {
)
.collect(Collectors.toList());
}
return schedule;
}

View File

@@ -6,17 +6,17 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.modules;
package com.facebook.openwifirrm.modules;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.text.ParseException;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronExpression;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
@@ -32,11 +32,10 @@ import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.RRMAlgorithm;
import com.facebook.openwifi.rrm.RRMSchedule;
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.RRMAlgorithm;
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -75,21 +74,15 @@ public class RRMScheduler {
/** The scheduler instance. */
private Scheduler scheduler;
/**
* The job keys with active triggers scheduled. Job keys take the format of
* {@code <zone>:<index>}
*
* @see #parseIntoQuartzCron(String)
* */
private Set<String> scheduledJobKeys;
/** The zones with active triggers scheduled. */
private Set<String> scheduledZones;
/** RRM job. */
public static class RRMJob implements Job {
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
String jobKey = context.getTrigger().getKey().getName();
String zone = jobKey.split(":")[0];
String zone = context.getTrigger().getKey().getName();
logger.debug("Executing job for zone: {}", zone);
try {
SchedulerContext schedulerContext =
@@ -114,14 +107,13 @@ public class RRMScheduler {
* @param linuxCron Linux cron with seconds
* (seconds minutes hours day_of_month month day_of_week [year])
*
* @throws IllegalArgumentException when a linux cron cannot be parsed into a
* valid Quartz spec
* @return String[] an array of length 1 or 2 of Quartz supported cron that's
* equivalent to the original linux cron
* @throws IllegalArgumentException when a linux cron cannot be parsed
* into a valid Quartz spec
* @return String a Quartz supported cron
*/
public static String[] parseIntoQuartzCron(String linuxCron) {
public static String parseIntoQuartzCron(String linuxCron) {
if (CronExpression.isValidExpression(linuxCron)) {
return new String[] { linuxCron };
return linuxCron;
}
String[] split = linuxCron.split(" ");
@@ -152,36 +144,15 @@ public class RRMScheduler {
// if first case failed and only day of week is *, set to ?
split[DAY_OF_WEEK_INDEX] = "?";
} else {
// Quartz does not support both values being set but the standard says that
// if both are specified then it becomes OR of the two fields. Which means
// that we can split it into two separate crons and have it work the same way
split[DAY_OF_MONTH_INDEX] = "?";
String dayOfWeekCron = String.join(" ", split);
split[DAY_OF_MONTH_INDEX] = dayOfMonth;
split[DAY_OF_WEEK_INDEX] = "?";
String dayOfMonthCron = String.join(" ", split);
if (
!CronExpression.isValidExpression(dayOfWeekCron) ||
!CronExpression.isValidExpression(dayOfMonthCron)
) {
logger.error(
"Unable to parse cron {} into valid crons",
linuxCron
);
return null;
}
return new String[] { dayOfWeekCron, dayOfMonthCron };
// Quartz does not support both values being set, so return null
return null;
}
String quartzCron = String.join(" ", split);
if (!CronExpression.isValidExpression(quartzCron)) {
return null;
}
return new String[] { quartzCron };
return quartzCron;
}
/** Constructor. */
@@ -223,7 +194,7 @@ public class RRMScheduler {
// Schedule job and triggers
scheduler.addJob(job, false);
syncTriggers();
logger.info("Scheduled {} RRM trigger(s)", scheduledJobKeys.size());
logger.info("Scheduled {} RRM trigger(s)", scheduledZones.size());
// Start scheduler
scheduler.start();
@@ -247,98 +218,85 @@ public class RRMScheduler {
/**
* Synchronize triggers to the current topology, adding/updating/deleting
* them as necessary. This updates {@link #scheduledJobKeys}.
* them as necessary. This updates {@link #scheduledZones}.
*/
public void syncTriggers() {
Set<String> scheduled = ConcurrentHashMap.newKeySet();
Set<String> prevScheduled = new HashSet<>();
if (scheduledJobKeys != null) {
prevScheduled.addAll(scheduledJobKeys);
if (scheduledZones != null) {
prevScheduled.addAll(scheduledZones);
}
// Add new triggers
for (String zone : deviceDataManager.getZones()) {
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
RRMSchedule schedule = config.schedule;
if (
schedule == null || schedule.crons == null ||
schedule.crons.isEmpty()
config.schedule == null ||
config.schedule.cron == null ||
config.schedule.cron.isEmpty()
) {
continue; // RRM not scheduled
}
for (int i = 0; i < schedule.crons.size(); i++) {
String cron = schedule.crons.get(i);
// if even one schedule has invalid cron, the whole thing is probably wrong
if (cron == null || cron.isEmpty()) {
logger.error("There was an invalid cron in the schedule");
break;
}
try {
CronExpression.validateExpression(cron);
} catch (ParseException e) {
logger.error(
String.format(
"Invalid cron expression (%s) for zone %s",
cron,
zone
),
e
);
continue;
}
// Create trigger
String jobKey = String.format("%s:%d", zone, i);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobKey)
.forJob(job)
.withSchedule(
CronScheduleBuilder.cronSchedule(cron)
)
.build();
try {
if (!prevScheduled.contains(jobKey)) {
scheduler.scheduleJob(trigger);
} else {
scheduler.rescheduleJob(trigger.getKey(), trigger);
}
} catch (SchedulerException e) {
logger.error(
"Failed to schedule RRM trigger for job key: " + jobKey,
e
);
continue;
}
scheduled.add(jobKey);
logger.debug(
"Scheduled/updated RRM for job key '{}' at: < {} >",
jobKey,
cron
);
}
}
// Remove old triggers
prevScheduled.removeAll(scheduled);
for (String jobKey : prevScheduled) {
try {
scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey));
} catch (SchedulerException e) {
CronExpression.validateExpression(config.schedule.cron);
} catch (ParseException e) {
logger.error(
"Failed to remove RRM trigger for jobKey: " + jobKey,
String.format(
"Invalid cron expression (%s) for zone %s",
config.schedule.cron,
zone
),
e
);
continue;
}
logger.debug("Removed RRM trigger for jobKey '{}'", jobKey);
// Create trigger
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(zone)
.forJob(job)
.withSchedule(
CronScheduleBuilder.cronSchedule(config.schedule.cron)
)
.build();
try {
if (!prevScheduled.contains(zone)) {
scheduler.scheduleJob(trigger);
} else {
scheduler.rescheduleJob(trigger.getKey(), trigger);
}
} catch (SchedulerException e) {
logger.error(
"Failed to schedule RRM trigger for zone: " + zone,
e
);
continue;
}
scheduled.add(zone);
logger.debug(
"Scheduled/updated RRM for zone '{}' at: < {} >",
zone,
config.schedule.cron
);
}
this.scheduledJobKeys = scheduled;
// Remove old triggers
prevScheduled.removeAll(scheduled);
for (String zone : prevScheduled) {
try {
scheduler.unscheduleJob(TriggerKey.triggerKey(zone));
} catch (SchedulerException e) {
logger.error(
"Failed to remove RRM trigger for zone: " + zone,
e
);
continue;
}
logger.debug("Removed RRM trigger for zone '{}'", zone);
}
this.scheduledZones = scheduled;
}
/** Run RRM algorithms for the given zone. */
@@ -347,19 +305,16 @@ public class RRMScheduler {
// Get algorithms from zone config
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
RRMSchedule schedule = config.schedule;
if (schedule == null) {
if (config.schedule == null) {
logger.error("RRM schedule missing for zone '{}', aborting!", zone);
return;
}
if (
schedule.algorithms == null ||
schedule.algorithms.isEmpty()
config.schedule.algorithms == null ||
config.schedule.algorithms.isEmpty()
) {
logger
.debug("Using default RRM algorithms for zone '{}'", zone);
schedule.algorithms = Arrays.asList(
logger.debug("Using default RRM algorithms for zone '{}'", zone);
config.schedule.algorithms = Arrays.asList(
new RRMAlgorithm(
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
),
@@ -370,15 +325,14 @@ public class RRMScheduler {
}
// Execute algorithms
for (RRMAlgorithm algo : schedule.algorithms) {
for (RRMAlgorithm algo : config.schedule.algorithms) {
RRMAlgorithm.AlgorithmResult result = algo.run(
deviceDataManager,
configManager,
modeler,
zone,
params.dryRun,
true, /* allowDefaultMode */
false /* updateImmediately */
true /* allowDefaultMode */
);
logger.info(
"'{}' result for zone '{}': {}",
@@ -387,6 +341,5 @@ public class RRMScheduler {
gson.toJson(result)
);
}
configManager.queueZoneAndWakeUp(zone);
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.mysql;
package com.facebook.openwifirrm.mysql;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -26,9 +26,9 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.Utils;
import com.facebook.openwifirrm.Utils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.models.State;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@@ -360,7 +360,7 @@ public class DatabaseManager {
/** Convert a list of state records to a State object. */
private State toState(List<StateRecord> records, long ts) {
State state = new State();
state.unit = new State.Unit();
state.unit = state.new Unit();
state.unit.localtime = ts;
// Parse each record

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.mysql;
package com.facebook.openwifirrm.mysql;
/**
* Representation of a record in the "state" table.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.channel;
package com.facebook.openwifirrm.optimizers.channel;
import java.util.ArrayList;
import java.util.Arrays;
@@ -18,18 +18,16 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralConstants;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.ConfigManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.google.gson.JsonObject;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.ConfigManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralConstants;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.models.State;
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
/**
* Channel optimizer base class.
@@ -41,6 +39,24 @@ public abstract class ChannelOptimizer {
/** Minimum supported channel width (MHz), inclusive. */
public static final int MIN_CHANNEL_WIDTH = 20;
/** List of available channels per band for use. */
public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND =
new HashMap<>();
static {
AVAILABLE_CHANNELS_BAND.put(
UCentralConstants.BAND_5G,
Collections.unmodifiableList(
Arrays.asList(36, 40, 44, 48, 149, 153, 157, 161, 165)
)
);
AVAILABLE_CHANNELS_BAND.put(
UCentralConstants.BAND_2G,
Collections.unmodifiableList(
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
)
);
}
/** Map of channel width (MHz) to available (primary) channels */
protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH =
new HashMap<>();
@@ -138,7 +154,7 @@ public abstract class ChannelOptimizer {
// Remove model entries not in the given zone
this.model.latestWifiScans.keySet()
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
this.model.latestStates.keySet()
this.model.latestState.keySet()
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
this.model.latestDeviceStatusRadios.keySet()
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
@@ -181,7 +197,7 @@ public abstract class ChannelOptimizer {
String vhtOper
) {
if (
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
.contains(channel)
) {
// 2.4G, it only supports 20 MHz
@@ -192,13 +208,13 @@ public abstract class ChannelOptimizer {
return MIN_CHANNEL_WIDTH;
}
HTOperation htOperObj = new HTOperation(htOper);
HTOperationElement htOperObj = new HTOperationElement(htOper);
if (vhtOper == null) {
// HT mode only supports 20/40 MHz
return htOperObj.staChannelWidth ? 40 : 20;
} else {
// VHT/HE mode supports 20/40/160/80+80 MHz
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) {
return 20;
} else if (
@@ -300,28 +316,25 @@ public abstract class ChannelOptimizer {
List<WifiScanEntry> scanRespsFiltered =
new ArrayList<WifiScanEntry>();
for (WifiScanEntry entry : scanResps) {
final String entryBand = UCentralUtils
.freqToBand(entry.frequency);
if (entryBand == null || !entryBand.equals(band)) {
continue;
}
int channelWidth = getChannelWidthFromWiFiScan(
entry.channel,
entry.ht_oper,
entry.vht_oper
);
int primaryChannel =
getPrimaryChannel(entry.channel, channelWidth);
List<Integer> coveredChannels =
getCoveredChannels(
if (UCentralUtils.isChannelInBand(entry.channel, band)) {
int channelWidth = getChannelWidthFromWiFiScan(
entry.channel,
primaryChannel,
channelWidth
entry.ht_oper,
entry.vht_oper
);
for (Integer newChannel : coveredChannels) {
WifiScanEntry newEntry = new WifiScanEntry(entry);
newEntry.channel = newChannel;
scanRespsFiltered.add(newEntry);
int primaryChannel =
getPrimaryChannel(entry.channel, channelWidth);
List<Integer> coveredChannels =
getCoveredChannels(
entry.channel,
primaryChannel,
channelWidth
);
for (Integer newChannel : coveredChannels) {
WifiScanEntry newEntry = new WifiScanEntry(entry);
newEntry.channel = newChannel;
scanRespsFiltered.add(newEntry);
}
}
}
@@ -349,7 +362,6 @@ public abstract class ChannelOptimizer {
* @param band the operational band (e.g., "2G")
* @param serialNumber the device's serial number
* @param state the latest state of all the devices
* @param latestDeviceCapabilities latest device capabilities
* @return the current channel and channel width (MHz) of the device in the
* given band; returns a current channel of 0 if no channel in the given
* band is found.
@@ -357,8 +369,7 @@ public abstract class ChannelOptimizer {
protected static int[] getCurrentChannel(
String band,
String serialNumber,
State state,
Map<String, JsonObject> latestDeviceCapabilities
State state
) {
int currentChannel = 0;
int currentChannelWidth = MIN_CHANNEL_WIDTH;
@@ -368,40 +379,27 @@ public abstract class ChannelOptimizer {
radioIndex < state.radios.length;
radioIndex++
) {
State.Radio radio = state.radios[radioIndex];
// check if radio is in band of interest
JsonObject deviceCapability =
latestDeviceCapabilities.get(serialNumber);
if (deviceCapability == null) {
continue;
}
final String radioBand = ModelerUtils.getBand(
radio,
deviceCapability
);
if (radioBand == null || !radioBand.equals(band)) {
continue;
}
int tempChannel = state.radios[radioIndex].channel;
if (UCentralUtils.isChannelInBand(tempChannel, band)) {
currentChannel = tempChannel;
// treat as two separate 80MHz channel and only assign to one
// TODO: support 80p80 properly
Integer parsedChannelWidth = UCentralUtils
.parseChannelWidth(
state.radios[radioIndex].channel_width,
true
);
if (parsedChannelWidth != null) {
currentChannelWidth = parsedChannelWidth;
break;
}
int tempChannel = radio.channel;
currentChannel = tempChannel;
// treat as two separate 80MHz channel and only assign to one
// TODO: support 80p80 properly
Integer parsedChannelWidth = UCentralUtils
.parseChannelWidth(
radio.channel_width,
true
logger.error(
"Invalid channel width {}",
state.radios[radioIndex].channel_width
);
if (parsedChannelWidth != null) {
currentChannelWidth = parsedChannelWidth;
break;
continue;
}
logger.error(
"Invalid channel width {}",
radio.channel_width
);
continue;
}
return new int[] { currentChannel, currentChannelWidth };
}
@@ -641,13 +639,14 @@ public abstract class ChannelOptimizer {
public abstract Map<String, Map<String, Integer>> computeChannelMap();
/**
* Program the given channel map into the AP config.
* Program the given channel map into the AP config and notify the config
* manager.
*
* @param deviceDataManager the DeviceDataManager instance
* @param configManager the ConfigManager instance
* @param channelMap the map of devices (by serial number) to radio to channel
*/
public void updateDeviceApConfig(
public void applyConfig(
DeviceDataManager deviceDataManager,
ConfigManager configManager,
Map<String, Map<String, Integer>> channelMap
@@ -665,5 +664,8 @@ public abstract class ChannelOptimizer {
deviceConfig.autoChannels = entry.getValue();
}
});
// Trigger config update now
configManager.wakeUp();
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.channel;
package com.facebook.openwifirrm.optimizers.channel;
import java.util.ArrayList;
import java.util.HashSet;
@@ -20,13 +20,12 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralConstants;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralConstants;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.models.State;
/**
* Least used channel optimizer.
@@ -90,13 +89,13 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
protected static Map<Integer, Integer> getOccupiedOverlapChannels(
Map<Integer, Integer> occupiedChannels
) {
final int maxChannel =
UCentralUtils.getUpperChannelLimit(UCentralConstants.BAND_2G);
final int minChannel =
UCentralUtils.getLowerChannelLimit(UCentralConstants.BAND_2G);
int maxChannel =
UCentralUtils.UPPER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
int minChannel =
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>();
for (
int overlapChannel : UCentralUtils.AVAILABLE_CHANNELS_BAND
int overlapChannel : AVAILABLE_CHANNELS_BAND
.get(UCentralConstants.BAND_2G)
) {
int occupancy = 0;
@@ -338,13 +337,11 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
UCentralUtils.getDeviceAvailableChannels(
model.latestDeviceStatusRadios,
model.latestDeviceCapabilities,
UCentralUtils.AVAILABLE_CHANNELS_BAND
AVAILABLE_CHANNELS_BAND
);
Map<String, State> latestState =
ModelerUtils.getLatestState(model.latestStates);
Map<String, String> bssidsMap =
UCentralUtils.getBssidsMap(latestState);
UCentralUtils.getBssidsMap(model.latestState);
for (String band : bandsMap.keySet()) {
// Performance metrics
@@ -372,12 +369,11 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
availableChannelsList == null ||
availableChannelsList.isEmpty()
) {
availableChannelsList =
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
}
// Get current channel of the device
State state = latestState.get(serialNumber);
State state = model.latestState.get(serialNumber);
if (state == null) {
logger.debug(
"Device {}: No state found, skipping...",
@@ -393,12 +389,7 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
continue;
}
int[] currentChannelInfo =
getCurrentChannel(
band,
serialNumber,
state,
model.latestDeviceCapabilities
);
getCurrentChannel(band, serialNumber, state);
int currentChannel = currentChannelInfo[0];
// Filter out APs if the radios in the state do not contain a
// channel in a band given by the state. This can happen when

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.channel;
package com.facebook.openwifirrm.optimizers.channel;
import java.util.ArrayList;
import java.util.List;
@@ -19,12 +19,11 @@ import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.models.State;
/**
* Random channel initializer.
@@ -126,13 +125,11 @@ public class RandomChannelInitializer extends ChannelOptimizer {
UCentralUtils.getDeviceAvailableChannels(
model.latestDeviceStatusRadios,
model.latestDeviceCapabilities,
UCentralUtils.AVAILABLE_CHANNELS_BAND
AVAILABLE_CHANNELS_BAND
);
Map<String, State> latestState =
ModelerUtils.getLatestState(model.latestStates);
Map<String, String> bssidsMap =
UCentralUtils.getBssidsMap(latestState);
UCentralUtils.getBssidsMap(model.latestState);
for (Map.Entry<String, List<String>> entry : bandsMap.entrySet()) {
// Performance metrics
@@ -152,7 +149,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
// to get the valid result for single channel assignment
// If the intersection is empty, then turn back to the default channels list
List<Integer> availableChannelsList = new ArrayList<>(
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band)
AVAILABLE_CHANNELS_BAND.get(band)
);
for (String serialNumber : entry.getValue()) {
List<Integer> deviceChannelsList = deviceAvailableChannels
@@ -161,16 +158,14 @@ public class RandomChannelInitializer extends ChannelOptimizer {
if (
deviceChannelsList == null || deviceChannelsList.isEmpty()
) {
deviceChannelsList =
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
deviceChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
}
availableChannelsList.retainAll(deviceChannelsList);
}
if (
availableChannelsList == null || availableChannelsList.isEmpty()
) {
availableChannelsList =
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
logger.debug(
"The intersection of the device channels lists is empty!!! " +
"Fall back to the default channels list"
@@ -188,7 +183,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
? rng.nextInt(availableChannelsList.size()) : defaultChannelIndex
);
State state = latestState.get(serialNumber);
State state = model.latestState.get(serialNumber);
if (state == null) {
logger.debug(
"Device {}: No state found, skipping...",
@@ -204,12 +199,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
continue;
}
int[] currentChannelInfo =
getCurrentChannel(
band,
serialNumber,
state,
model.latestDeviceCapabilities
);
getCurrentChannel(band, serialNumber, state);
int currentChannel = currentChannelInfo[0];
int currentChannelWidth = currentChannelInfo[1];
if (currentChannel == 0) {

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.channel;
package com.facebook.openwifirrm.optimizers.channel;
import java.util.ArrayList;
import java.util.HashMap;
@@ -17,10 +17,10 @@ import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralConstants;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralConstants;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
/**
* Unmanaged AP aware least used channel optimizer.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.tpc;
package com.facebook.openwifirrm.optimizers.tpc;
import java.util.ArrayList;
import java.util.Collections;
@@ -18,11 +18,12 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.modules.ModelerUtils;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.models.State;
/**
* Location-based optimal TPC algorithm.
@@ -152,7 +153,6 @@ public class LocationBasedOptimalTPC extends TPC {
/**
* Calculate new tx powers for the given band.
*
* @param band band (e.g., "2G")
* @param channel channel
* @param serialNumbers The serial numbers of the APs with the channel
* @param txPowerMap this map from serial number to band to new tx power
@@ -160,13 +160,13 @@ public class LocationBasedOptimalTPC extends TPC {
* this method with the new tx powers.
*/
private void buildTxPowerMapForChannel(
String band,
int channel,
List<String> serialNumbers,
Map<String, Map<String, Integer>> txPowerMap
) {
int numOfAPs = 0;
int boundary = 100;
String band = UCentralUtils.getBandFromChannel(channel);
Map<String, Integer> validAPs = new TreeMap<>();
List<Double> apLocX = new ArrayList<>();
List<Double> apLocY = new ArrayList<>();
@@ -175,8 +175,7 @@ public class LocationBasedOptimalTPC extends TPC {
// Filter out the invalid APs (e.g., no radio, no location data)
// Update txPowerChoices, boundary, apLocX, apLocY for the optimization
for (String serialNumber : serialNumbers) {
List<State> states = model.latestStates.get(serialNumber);
State state = states.get(states.size() - 1);
State state = model.latestState.get(serialNumber);
// Ignore the device if its radio is not active
if (state.radios == null || state.radios.length == 0) {
@@ -283,27 +282,9 @@ public class LocationBasedOptimalTPC extends TPC {
@Override
public Map<String, Map<String, Integer>> computeTxPowerMap() {
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
Map<String, Map<Integer, List<String>>> bandToChannelToAps =
getApsPerChannel();
for (
Map.Entry<String, Map<Integer, List<String>>> bandEntry : bandToChannelToAps
.entrySet()
) {
final String band = bandEntry.getKey();
Map<Integer, List<String>> channelToAps = bandEntry.getValue();
for (
Map.Entry<Integer, List<String>> channelEntry : channelToAps
.entrySet()
) {
final int channel = channelEntry.getKey();
List<String> serialNumbers = channelEntry.getValue();
buildTxPowerMapForChannel(
band,
channel,
serialNumbers,
txPowerMap
);
}
Map<Integer, List<String>> apsPerChannel = getApsPerChannel();
for (Map.Entry<Integer, List<String>> e : apsPerChannel.entrySet()) {
buildTxPowerMapForChannel(e.getKey(), e.getValue(), txPowerMap);
}
return txPowerMap;
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.tpc;
package com.facebook.openwifirrm.optimizers.tpc;
import java.util.ArrayList;
import java.util.Collections;
@@ -20,13 +20,11 @@ import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.google.gson.JsonObject;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
import com.facebook.openwifirrm.ucentral.models.State;
/**
* Measurement-based AP-AP TPC algorithm.
@@ -156,9 +154,8 @@ public class MeasurementBasedApApTPC extends TPC {
*/
protected static Set<String> getManagedBSSIDs(DataModel model) {
Set<String> managedBSSIDs = new HashSet<>();
for (Map.Entry<String, List<State>> e : model.latestStates.entrySet()) {
List<State> states = e.getValue();
State state = states.get(states.size() - 1);
for (Map.Entry<String, State> e : model.latestState.entrySet()) {
State state = e.getValue();
if (state.interfaces == null) {
continue;
}
@@ -219,14 +216,7 @@ public class MeasurementBasedApApTPC extends TPC {
// At a given AP, if we receive a signal from ap_2, then it gets added to the rssi list for ap_2
latestScan.stream()
.filter((entry) -> {
if (!managedBSSIDs.contains(entry.bssid)) {
return false;
}
String entryBand = UCentralUtils
.freqToBand(entry.frequency);
return entryBand != null && entryBand.equals(band);
})
.filter(entry -> (managedBSSIDs.contains(entry.bssid) && UCentralUtils.isChannelInBand(entry.channel, band)))
.forEach(
entry -> {
bssidToRssiValues.get(entry.bssid).add(entry.signal);
@@ -305,25 +295,23 @@ public class MeasurementBasedApApTPC extends TPC {
/**
* Calculate new tx powers for the given channel on the given APs .
*
* @param band band (e.g., "2G")
* @param channel channel
* @param serialNumbers the serial numbers of the APs with the channel
* @param txPowerMap this maps from serial number to band to new tx power (dBm)
* and is updated by this method with the new tx powers.
*/
protected void buildTxPowerMapForChannel(
String band,
int channel,
List<String> serialNumbers,
Map<String, Map<String, Integer>> txPowerMap
) {
String band = UCentralUtils.getBandFromChannel(channel);
Set<String> managedBSSIDs = getManagedBSSIDs(model);
Map<String, List<Integer>> bssidToRssiValues =
buildRssiMap(managedBSSIDs, model.latestWifiScans, band);
logger.debug("Starting TPC for the {} band", band);
for (String serialNumber : serialNumbers) {
List<State> states = model.latestStates.get(serialNumber);
State state = states.get(states.size() - 1);
State state = model.latestState.get(serialNumber);
if (
state == null || state.radios == null ||
state.radios.length == 0
@@ -373,16 +361,9 @@ public class MeasurementBasedApApTPC extends TPC {
State.Radio radio = state.radios[idx];
// this specific SSID is not on the band of interest
JsonObject deviceCapability = model.latestDeviceCapabilities
.get(serialNumber);
if (deviceCapability == null) {
continue;
}
final String radioBand = ModelerUtils.getBand(
radio,
deviceCapability
);
if (radioBand == null || !radioBand.equals(band)) {
if (
!UCentralUtils.isChannelInBand(radio.channel, band)
) {
continue;
}
@@ -426,27 +407,9 @@ public class MeasurementBasedApApTPC extends TPC {
@Override
public Map<String, Map<String, Integer>> computeTxPowerMap() {
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
Map<String, Map<Integer, List<String>>> bandToChannelToAps =
getApsPerChannel();
for (
Map.Entry<String, Map<Integer, List<String>>> bandEntry : bandToChannelToAps
.entrySet()
) {
final String band = bandEntry.getKey();
Map<Integer, List<String>> channelToAps = bandEntry.getValue();
for (
Map.Entry<Integer, List<String>> channelEntry : channelToAps
.entrySet()
) {
final int channel = channelEntry.getKey();
List<String> serialNumbers = channelEntry.getValue();
buildTxPowerMapForChannel(
band,
channel,
serialNumbers,
txPowerMap
);
}
Map<Integer, List<String>> apsPerChannel = getApsPerChannel();
for (Map.Entry<Integer, List<String>> e : apsPerChannel.entrySet()) {
buildTxPowerMapForChannel(e.getKey(), e.getValue(), txPowerMap);
}
return txPowerMap;
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.tpc;
package com.facebook.openwifirrm.optimizers.tpc;
import java.util.ArrayList;
import java.util.Arrays;
@@ -15,15 +15,13 @@ import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralUtils;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.google.gson.JsonObject;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.models.State;
/**
* Measurement-based AP-client algorithm.
@@ -293,10 +291,10 @@ public class MeasurementBasedApClientTPC extends TPC {
public Map<String, Map<String, Integer>> computeTxPowerMap() {
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
for (Map.Entry<String, List<State>> e : model.latestStates.entrySet()) {
for (Map.Entry<String, State> e : model.latestState.entrySet()) {
String serialNumber = e.getKey();
List<State> states = e.getValue();
State state = states.get(states.size() - 1);
State state = e.getValue();
if (state.radios == null || state.radios.length == 0) {
logger.debug(
"Device {}: No radios found, skipping...",
@@ -307,15 +305,8 @@ public class MeasurementBasedApClientTPC extends TPC {
Map<String, Integer> radioMap = new TreeMap<>();
for (State.Radio radio : state.radios) {
JsonObject deviceCapability = model.latestDeviceCapabilities
.get(serialNumber);
if (deviceCapability == null) {
continue;
}
final String band = ModelerUtils.getBand(
radio,
deviceCapability
);
int currentChannel = radio.channel;
String band = UCentralUtils.getBandFromChannel(currentChannel);
if (band == null) {
continue;
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.tpc;
package com.facebook.openwifirrm.optimizers.tpc;
import java.util.ArrayList;
import java.util.List;
@@ -14,12 +14,13 @@ import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import com.facebook.openwifirrm.ucentral.UCentralUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralConstants;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.UCentralConstants;
/**
* Random tx power initializer.
@@ -121,7 +122,7 @@ public class RandomTxPowerInitializer extends TPC {
if (!setDifferentTxPowerPerAp) {
List<Integer> txPowerChoices =
new ArrayList<>(DEFAULT_TX_POWER_CHOICES);
for (String serialNumber : model.latestStates.keySet()) {
for (String serialNumber : model.latestState.keySet()) {
for (String band : UCentralConstants.BANDS) {
txPowerChoices = updateTxPowerChoices(
band,
@@ -136,44 +137,36 @@ public class RandomTxPowerInitializer extends TPC {
logger.info("Default power: {}", defaultTxPower);
}
Map<String, Map<Integer, List<String>>> bandToChannelToAps =
getApsPerChannel();
Map<Integer, List<String>> apsPerChannel = getApsPerChannel();
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
for (
Map.Entry<String, Map<Integer, List<String>>> bandEntry : bandToChannelToAps
.entrySet()
) {
final String band = bandEntry.getKey();
Map<Integer, List<String>> channelToAps = bandEntry.getValue();
for (
Map.Entry<Integer, List<String>> channelEntry : channelToAps
.entrySet()
) {
List<String> serialNumbers = channelEntry.getValue();
for (String serialNumber : serialNumbers) {
int txPower = defaultTxPower;
if (setDifferentTxPowerPerAp) {
List<Integer> curTxPowerChoices = updateTxPowerChoices(
band,
serialNumber,
DEFAULT_TX_POWER_CHOICES
);
txPower = curTxPowerChoices
.get(rng.nextInt(curTxPowerChoices.size()));
}
txPowerMap
.computeIfAbsent(serialNumber, k -> new TreeMap<>())
.put(band, txPower);
logger.info(
"Device {} band {}: Assigning tx power = {}",
serialNumber,
for (Map.Entry<Integer, List<String>> e : apsPerChannel.entrySet()) {
int channel = e.getKey();
List<String> serialNumbers = e.getValue();
String band = UCentralUtils.getBandFromChannel(channel);
for (String serialNumber : serialNumbers) {
int txPower = defaultTxPower;
if (setDifferentTxPowerPerAp) {
List<Integer> curTxPowerChoices = updateTxPowerChoices(
band,
txPower
serialNumber,
DEFAULT_TX_POWER_CHOICES
);
txPower = curTxPowerChoices
.get(rng.nextInt(curTxPowerChoices.size()));
}
txPowerMap.computeIfAbsent(serialNumber, k -> new TreeMap<>())
.put(band, txPower);
logger.info(
"Device {} band {}: Assigning tx power = {}",
serialNumber,
band,
txPower
);
}
}
return txPowerMap;
}
}

View File

@@ -6,28 +6,27 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.rrm.optimizers.tpc;
package com.facebook.openwifirrm.optimizers.tpc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.modules.ConfigManager;
import com.facebook.openwifirrm.modules.Modeler.DataModel;
import com.facebook.openwifirrm.ucentral.models.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifi.rrm.DeviceConfig;
import com.facebook.openwifi.rrm.DeviceDataManager;
import com.facebook.openwifi.rrm.modules.ConfigManager;
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
import com.facebook.openwifi.rrm.modules.ModelerUtils;
import com.google.gson.JsonObject;
/**
* TPC (Transmit Power Control) base class.
*/
@@ -72,7 +71,7 @@ public abstract class TPC {
this.model.latestWifiScans.keySet()
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)
);
this.model.latestStates.keySet()
this.model.latestState.keySet()
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)
);
this.model.latestDeviceStatusRadios.keySet()
@@ -84,19 +83,19 @@ public abstract class TPC {
}
/**
* Determine the new tx power choices based on user and allowed channels from deviceConfig.
*
* Update the tx power choices based on user and allowed channels from deviceConfig
* @param band the operational band
* @param serialNumber the device's serial number
* @param txPowerChoices the device's available tx powers
* @return the device's updated tx powers
* @param serialNumber the device
* @param txPowerChoices the available tx powers of the device
* @return the updated tx powers of the device
*/
protected List<Integer> updateTxPowerChoices(
String band,
String serialNumber,
List<Integer> txPowerChoices
) {
List<Integer> newTxPowerChoices = new ArrayList<>(txPowerChoices);
List<Integer> newTxPowerChoices =
new ArrayList<>(txPowerChoices);
// Update the available tx powers based on user tx powers or allowed tx powers
DeviceConfig deviceCfg = deviceConfigs.get(serialNumber);
@@ -128,7 +127,8 @@ public abstract class TPC {
newTxPowerChoices.retainAll(allowedTxPowers);
}
// If newTxPowerChoices is empty, use default available tx powers list
// If the intersection of the above steps gives an empty list,
// turn back to use the default available tx powers list
if (newTxPowerChoices.isEmpty()) {
logger.debug(
"Device {}: the updated availableTxPowersList is empty!!! " +
@@ -153,13 +153,14 @@ public abstract class TPC {
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
/**
* Program the given tx power map into the AP config.
* Program the given tx power map into the AP config and notify the config
* manager.
*
* @param deviceDataManager the DeviceDataManager instance
* @param configManager the ConfigManager instance
* @param txPowerMap the map of devices (by serial number) to radio to tx power
*/
public void updateDeviceApConfig(
public void applyConfig(
DeviceDataManager deviceDataManager,
ConfigManager configManager,
Map<String, Map<String, Integer>> txPowerMap
@@ -177,19 +178,21 @@ public abstract class TPC {
deviceConfig.autoTxPowers = entry.getValue();
}
});
// Trigger config update now
configManager.wakeUp();
}
/**
* Get AP serial numbers per channel.
*
* @return map from band to channel to list of AP serial numbers
* @return the map of channel to the list of serial numbers
*/
protected Map<String, Map<Integer, List<String>>> getApsPerChannel() {
Map<String, Map<Integer, List<String>>> apsPerChannel = new TreeMap<>();
for (Map.Entry<String, List<State>> e : model.latestStates.entrySet()) {
protected Map<Integer, List<String>> getApsPerChannel() {
Map<Integer, List<String>> apsPerChannel = new TreeMap<>();
for (Map.Entry<String, State> e : model.latestState.entrySet()) {
String serialNumber = e.getKey();
List<State> states = e.getValue();
State state = states.get(states.size() - 1);
State state = e.getValue();
if (state.radios == null || state.radios.length == 0) {
logger.debug(
@@ -204,20 +207,7 @@ public abstract class TPC {
if (currentChannel == 0) {
continue;
}
JsonObject deviceCapability =
model.latestDeviceCapabilities.get(serialNumber);
if (deviceCapability == null) {
continue;
}
final String band = ModelerUtils.getBand(
radio,
deviceCapability
);
if (band == null) {
continue;
}
apsPerChannel
.computeIfAbsent(band, k -> new TreeMap<>())
.computeIfAbsent(currentChannel, k -> new ArrayList<>())
.add(serialNumber);
}

View File

@@ -6,13 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.kafka;
package com.facebook.openwifirrm.ucentral;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.kafka.common.errors.WakeupException;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
/**
* Kafka runner.

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
package com.facebook.openwifirrm.ucentral;
import java.util.HashSet;
import java.util.Set;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
package com.facebook.openwifirrm.ucentral;
import java.util.Collections;
import java.util.HashMap;
@@ -20,21 +20,22 @@ import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.gw.WifiScanRequest;
import com.facebook.openwifi.cloudsdk.models.prov.EntityList;
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList;
import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails;
import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList;
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
import com.facebook.openwifirrm.RRMConfig.UCentralConfig.UCentralSocketParams;
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceConfigureRequest;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceListWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest;
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails;
import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList;
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
@@ -126,14 +127,8 @@ public class UCentralClient {
/** uCentral password */
private final String password;
/** Connection timeout for all requests, in ms */
private final int connectTimeoutMs;
/** Socket timeout for all requests, in ms */
private final int socketTimeoutMs;
/** Socket timeout for wifi scan requests, in ms */
private final int wifiScanTimeoutMs;
/** Socket parameters */
private final UCentralSocketParams socketParams;
/** The learned service endpoints. */
private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>();
@@ -152,9 +147,7 @@ public class UCentralClient {
* (if needed)
* @param username uCentral username (for public endpoints only)
* @param password uCentral password (for public endpoints only)
* @param connectTimeoutMs connection timeout for all requests, in ms
* @param socketTimeoutMs socket timeout for all requests, in ms
* @param wifiScanTimeoutMs socket timeout for wifi scan requests, in ms
* @param socketParams Socket parameters
*/
public UCentralClient(
String rrmEndpoint,
@@ -162,17 +155,13 @@ public class UCentralClient {
String uCentralSecPublicEndpoint,
String username,
String password,
int connectTimeoutMs,
int socketTimeoutMs,
int wifiScanTimeoutMs
UCentralSocketParams socketParams
) {
this.rrmEndpoint = rrmEndpoint;
this.usePublicEndpoints = usePublicEndpoints;
this.username = username;
this.password = password;
this.connectTimeoutMs = connectTimeoutMs;
this.socketTimeoutMs = socketTimeoutMs;
this.wifiScanTimeoutMs = wifiScanTimeoutMs;
this.socketParams = socketParams;
if (usePublicEndpoints) {
setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint);
@@ -316,8 +305,8 @@ public class UCentralClient {
endpoint,
service,
parameters,
connectTimeoutMs,
socketTimeoutMs
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
);
}
@@ -360,8 +349,8 @@ public class UCentralClient {
endpoint,
service,
body,
connectTimeoutMs,
socketTimeoutMs
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
);
}
@@ -465,8 +454,8 @@ public class UCentralClient {
String.format("device/%s/wifiscan", serialNumber),
OWGW_SERVICE,
req,
connectTimeoutMs,
wifiScanTimeoutMs
socketParams.connectTimeoutMs,
socketParams.wifiScanTimeoutMs
);
if (!response.isSuccess()) {
logger.error("Error: {}", response.getBody());

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
package com.facebook.openwifirrm.ucentral;
import java.util.Arrays;
import java.util.Collections;
@@ -22,7 +22,7 @@ public final class UCentralConstants {
public static final String BAND_2G = "2G";
/** String of the 5 GHz band */
public static final String BAND_5G = "5G";
/** List of all bands ordered from lowest to highest */
/** List of all bands */
public static final List<String> BANDS = Collections
.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G));

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.kafka;
package com.facebook.openwifirrm.ucentral;
import java.time.Duration;
import java.util.ArrayList;
@@ -29,8 +29,7 @@ import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.UCentralClient;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.kafka;
package com.facebook.openwifirrm.ucentral;
import java.util.Properties;
@@ -18,7 +18,7 @@ import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.google.gson.Gson;
/**

View File

@@ -6,11 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
package com.facebook.openwifirrm.ucentral;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -21,11 +21,10 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifi.cloudsdk.ies.Country;
import com.facebook.openwifi.cloudsdk.ies.LocalPowerConstraint;
import com.facebook.openwifi.cloudsdk.ies.QbssLoad;
import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo;
import com.facebook.openwifi.cloudsdk.models.ap.State;
import com.facebook.openwifirrm.RRMConfig;
import com.facebook.openwifirrm.Utils;
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
import com.facebook.openwifirrm.ucentral.models.State;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@@ -38,53 +37,28 @@ public class UCentralUtils {
private static final Logger logger =
LoggerFactory.getLogger(UCentralUtils.class);
/** Information Element (IE) content field key */
private static final String IE_CONTENT_FIELD_KEY = "content";
/** The Gson instance. */
private static final Gson gson = new Gson();
/** Map from band to ordered (increasing) list of available channels */
public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND =
Collections
.unmodifiableMap(buildBandToChannelsMap());
/** Map of band to the band-specific lowest available channel*/
public static final Map<String, Integer> LOWER_CHANNEL_LIMIT =
new HashMap<>();
static {
UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 1);
UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 36);
}
/** Map of band to the band-specific highest available channel*/
public static final Map<String, Integer> UPPER_CHANNEL_LIMIT =
new HashMap<>();
static {
UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 11);
UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 165);
}
// This class should not be instantiated.
private UCentralUtils() {}
/**
* Builds map from band to ordered (increasing) list of available channels.
*/
private static Map<String, List<Integer>> buildBandToChannelsMap() {
Map<String, List<Integer>> bandToChannelsMap = new HashMap<>();
bandToChannelsMap.put(
UCentralConstants.BAND_5G,
Collections.unmodifiableList(
Arrays.asList(36, 40, 44, 48, 149, 153, 157, 161, 165)
)
);
// NOTE: later, we may want to support channels 12, 13, and/or 14, if
// the AP supports it and OWF vendors will use them
bandToChannelsMap.put(
UCentralConstants.BAND_2G,
Collections.unmodifiableList(
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
)
);
return bandToChannelsMap;
}
/** Return the lowest available channel for the given band */
public static int getLowerChannelLimit(String band) {
return AVAILABLE_CHANNELS_BAND.get(band).get(0);
}
/** Return the lowest available channel for the given band */
public static int getUpperChannelLimit(String band) {
List<Integer> channels = AVAILABLE_CHANNELS_BAND.get(band);
return channels.get(channels.size() - 1);
}
/**
* Parse a JSON wifi scan result into a list of WifiScanEntry objects.
*
@@ -105,76 +79,15 @@ public class UCentralUtils {
for (JsonElement e : scanInfo) {
WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class);
entry.unixTimeMs = timestampMs;
extractIEs(e, entry);
entries.add(entry);
}
} catch (Exception e) {
logger.debug("Exception when parsing wifiscan entries", e);
return null;
}
return entries;
}
/**
* Extract desired information elements (IEs) from the wifiscan entry.
* Modifies {@code entry} argument. Skips invalid IEs (IEs with missing
* fields).
*/
private static void extractIEs(
JsonElement entryJsonElement,
WifiScanEntry entry
) {
JsonElement iesJsonElement =
entryJsonElement.getAsJsonObject().get("ies");
if (iesJsonElement == null) {
logger.debug("Wifiscan entry does not contain 'ies' field.");
return;
}
JsonArray iesJsonArray = iesJsonElement.getAsJsonArray();
InformationElements ieContainer = new InformationElements();
for (JsonElement ieJsonElement : iesJsonArray) {
JsonElement typeElement =
ieJsonElement.getAsJsonObject().get("type");
if (typeElement == null) { // shouldn't happen
continue;
}
if (!ieJsonElement.isJsonObject()) {
// the IEs we are interested in are Json objects
continue;
}
JsonObject ie = ieJsonElement.getAsJsonObject();
JsonElement contentsJsonElement = ie.get(IE_CONTENT_FIELD_KEY);
if (
contentsJsonElement == null ||
!contentsJsonElement.isJsonObject()
) {
continue;
}
JsonObject contents = contentsJsonElement.getAsJsonObject();
try {
switch (typeElement.getAsInt()) {
case Country.TYPE:
ieContainer.country = Country.parse(contents);
break;
case QbssLoad.TYPE:
ieContainer.qbssLoad = QbssLoad.parse(contents);
break;
case LocalPowerConstraint.TYPE:
ieContainer.localPowerConstraint =
LocalPowerConstraint.parse(contents);
break;
case TxPwrInfo.TYPE:
ieContainer.txPwrInfo = TxPwrInfo.parse(contents);
break;
}
} catch (Exception e) {
logger.error(String.format("Skipping invalid IE %s", ie), e);
continue;
}
}
entry.ieContainer = ieContainer;
}
/**
* Set all radios config of an AP to a given value.
*
@@ -418,6 +331,47 @@ public class UCentralUtils {
return bssidMap;
}
/** Generate the RRM service key. */
public static String generateServiceKey(
RRMConfig.ServiceConfig serviceConfig
) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(serviceConfig.publicEndpoint.getBytes());
sha256.update(serviceConfig.privateEndpoint.getBytes());
return Utils.bytesToHex(sha256.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("Unable to generate service key", e);
return "";
}
}
/**
* Converts channel number to that channel's center frequency in MHz.
*
* @param channel channel number. See
* {@link ChannelOptimizer#AVAILABLE_CHANNELS_BAND} for channels
* in each band.
* @return the center frequency of the given channel in MHz
*/
public static int channelToFrequencyMHz(int channel) {
if (
ChannelOptimizer.AVAILABLE_CHANNELS_BAND
.get(UCentralConstants.BAND_2G)
.contains(channel)
) {
return 2407 + 5 * channel;
} else if (
ChannelOptimizer.AVAILABLE_CHANNELS_BAND
.get(UCentralConstants.BAND_5G)
.contains(channel)
) {
return 5000 + channel;
} else {
throw new IllegalArgumentException("Must provide a valid channel.");
}
}
/**
* Determines if the given channel is in the given band.
*
@@ -426,16 +380,24 @@ public class UCentralUtils {
* @return true if the given channel is in the given band; false otherwise
*/
public static boolean isChannelInBand(int channel, String band) {
return AVAILABLE_CHANNELS_BAND.get(band).contains(channel);
return LOWER_CHANNEL_LIMIT.get(band) <= channel &&
channel <= UPPER_CHANNEL_LIMIT.get(band);
}
/** Return which band contains the given frequency (MHz). */
public static String freqToBand(int freqMHz) {
if (2412 <= freqMHz && freqMHz <= 2484) {
return "2G";
} else {
return "5G";
/**
* Given the channel, gets the band by checking lower bound and upper bound
* of each band
*
* @param channel channel number
* @return band if the channel can be mapped to a valid band; null otherwise
*/
public static String getBandFromChannel(int channel) {
for (String band : UCentralConstants.BANDS) {
if (isChannelInBand(channel, band)) {
return band;
}
}
return null;
}
/**

View File

@@ -6,11 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk;
package com.facebook.openwifirrm.ucentral;
import java.util.Objects;
import com.facebook.openwifi.cloudsdk.models.ap.WifiScanEntryResult;
import com.facebook.openwifirrm.ucentral.models.WifiScanEntryResult;
/**
* Extends {@link WifiScanEntryResult} to track the response time of the entry.
@@ -22,8 +22,6 @@ public class WifiScanEntry extends WifiScanEntryResult {
* time reference.
*/
public long unixTimeMs;
/** Stores Information Elements (IEs) from the wifiscan entry. */
public InformationElements ieContainer;
/** Default Constructor. */
public WifiScanEntry() {}
@@ -32,14 +30,13 @@ public class WifiScanEntry extends WifiScanEntryResult {
public WifiScanEntry(WifiScanEntry o) {
super(o);
this.unixTimeMs = o.unixTimeMs;
this.ieContainer = o.ieContainer;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(ieContainer, unixTimeMs);
result = prime * result + Objects.hash(unixTimeMs);
return result;
}
@@ -55,8 +52,7 @@ public class WifiScanEntry extends WifiScanEntryResult {
return false;
}
WifiScanEntry other = (WifiScanEntry) obj;
return Objects.equals(ieContainer, other.ieContainer) &&
unixTimeMs == other.unixTimeMs;
return unixTimeMs == other.unixTimeMs;
}
@Override

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class AclTemplate {
public boolean Read;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import com.google.gson.JsonObject;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import com.google.gson.JsonObject;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class DeviceConfigureRequest {
public String serialNumber;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,6 +6,6 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public enum DeviceType { AP, SWITCH, IOT, MESH }

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class MfaAuthInfo {
public boolean enabled;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class MobilePhoneNumber {
public String number;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class NoteInfo {
public long created;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class ServiceEvent {
public static final String EVENT_JOIN = "join";

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import com.google.gson.JsonObject;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class TokenValidationResult {
public UserInfo userInfo;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public enum VerifiedCertificate {
NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class WebTokenAclTemplate {
public AclTemplate aclTemplate;

View File

@@ -6,13 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
public class WebTokenResult {
public String access_token;
public String refresh_token;
public String token_type;
public long expires_in;
public int expires_in;
public int idle_timeout;
public String username;
public long created;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.gw;
package com.facebook.openwifirrm.ucentral.gw.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.ap;
package com.facebook.openwifirrm.ucentral.models;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;

View File

@@ -6,14 +6,14 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.ap;
package com.facebook.openwifirrm.ucentral.models;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
public class State {
public static class Interface {
public static class Client {
public class Interface {
public class Client {
public String mac;
public String[] ipv4_addresses;
public String[] ipv6_addresses;
@@ -21,9 +21,9 @@ public class State {
// TODO last_seen
}
public static class SSID {
public static class Association {
public static class Rate {
public class SSID {
public class Association {
public class Rate {
public long bitrate;
public int chwidth;
public boolean sgi;
@@ -66,7 +66,7 @@ public class State {
public JsonObject radio;
}
public static class Counters {
public class Counters {
public long collisions;
public long multicast;
public long rx_bytes;
@@ -96,8 +96,8 @@ public class State {
public Interface[] interfaces;
public static class Unit {
public static class Memory {
public class Unit {
public class Memory {
public long buffered;
public long cached;
public long free;

View File

@@ -6,15 +6,13 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.ap;
package com.facebook.openwifirrm.ucentral.models;
import java.util.Objects;
/**
* Represents a single entry in wifi scan results.
* ies[] array is not stored directly, but parsed into WifiScanEntry fields
*
*/
import com.google.gson.JsonArray;
/** Represents a single entry in wifi scan results. */
public class WifiScanEntryResult {
public int channel;
public long last_seen;
@@ -52,6 +50,8 @@ public class WifiScanEntryResult {
public String vht_oper;
public int capability;
public int frequency;
/** IE = information element */
public JsonArray ies;
/** Default Constructor. */
public WifiScanEntryResult() {}
@@ -68,6 +68,7 @@ public class WifiScanEntryResult {
this.vht_oper = o.vht_oper;
this.capability = o.capability;
this.frequency = o.frequency;
this.ies = o.ies;
}
@Override
@@ -78,6 +79,7 @@ public class WifiScanEntryResult {
channel,
frequency,
ht_oper,
ies,
last_seen,
signal,
ssid,
@@ -102,9 +104,7 @@ public class WifiScanEntryResult {
capability == other.capability && channel == other.channel &&
frequency == other.frequency && Objects
.equals(ht_oper, other.ht_oper) &&
last_seen == other.last_seen &&
Objects.equals(ssid, other.ssid) && tsf == other.tsf &&
Objects.equals(vht_oper, other.vht_oper);
Objects.equals(ies, other.ies) && last_seen == other.last_seen && signal == other.signal && Objects.equals(ssid, other.ssid) && tsf == other.tsf && Objects.equals(vht_oper, other.vht_oper);
}
@Override

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
package com.facebook.openwifirrm.ucentral.operationelement;
import java.util.Arrays;
import java.util.Objects;
@@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64;
* High Throughput (HT) Operation Element, which is potentially present in
* wifiscan entries. Introduced in 802.11n (2009).
*/
public class HTOperation {
public class HTOperationElement {
/** Channel number of the primary channel. */
public final byte primaryChannel;
@@ -78,7 +78,7 @@ public class HTOperation {
* For details about the parameters, see the javadocs for the corresponding
* member variables.
*/
public HTOperation(
public HTOperationElement(
byte primaryChannel,
byte secondaryChannelOffset,
boolean staChannelWidth,
@@ -114,7 +114,7 @@ public class HTOperation {
}
/** Constructor with the most used parameters. */
public HTOperation(
public HTOperationElement(
byte primaryChannel,
byte secondaryChannelOffset,
boolean staChannelWidth,
@@ -141,7 +141,7 @@ public class HTOperation {
* @param htOper a base64 encoded properly formatted HT operation element (see
* 802.11)
*/
public HTOperation(String htOper) {
public HTOperationElement(String htOper) {
byte[] bytes = Base64.decodeBase64(htOper);
/*
* Note that the code here may seem to read "reversed" compared to 802.11. This
@@ -182,7 +182,7 @@ public class HTOperation {
* @return true if the the operation elements "match" for the purpose of
* aggregating statistics; false otherwise.
*/
public boolean matchesForAggregation(HTOperation other) {
public boolean matchesForAggregation(HTOperationElement other) {
return other != null && primaryChannel == other.primaryChannel &&
secondaryChannelOffset == other.secondaryChannelOffset &&
staChannelWidth == other.staChannelWidth &&
@@ -211,8 +211,8 @@ public class HTOperation {
if (htOper1 == null || htOper2 == null) {
return false; // false if exactly one is null
}
HTOperation htOperObj1 = new HTOperation(htOper1);
HTOperation htOperObj2 = new HTOperation(htOper2);
HTOperationElement htOperObj1 = new HTOperationElement(htOper1);
HTOperationElement htOperObj2 = new HTOperationElement(htOper2);
return htOperObj1.matchesForAggregation(htOperObj2);
}
@@ -248,7 +248,7 @@ public class HTOperation {
if (getClass() != obj.getClass()) {
return false;
}
HTOperation other = (HTOperation) obj;
HTOperationElement other = (HTOperationElement) obj;
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
channelCenterFrequencySegment2 ==
other.channelCenterFrequencySegment2 &&

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.ies;
package com.facebook.openwifirrm.ucentral.operationelement;
import java.util.Arrays;
import java.util.Objects;
@@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64;
* Very High Throughput (VHT) Operation Element, which is potentially present in
* wifiscan entries. Introduced in 802.11ac (2013).
*/
public class VHTOperation {
public class VHTOperationElement {
/**
* This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise.
@@ -65,7 +65,7 @@ public class VHTOperation {
* @param vhtOper a base64 encoded properly formatted VHT operation element (see
* 802.11 standard)
*/
public VHTOperation(String vhtOper) {
public VHTOperationElement(String vhtOper) {
byte[] bytes = Base64.decodeBase64(vhtOper);
this.channelWidth = bytes[0];
this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
@@ -89,7 +89,7 @@ public class VHTOperation {
* For details about the parameters, see the javadocs for the corresponding
* member variables.
*/
public VHTOperation(
public VHTOperationElement(
byte channelWidth,
short channel1,
short channel2,
@@ -114,7 +114,7 @@ public class VHTOperation {
* @return true if the the operation elements "match" for the purpose of
* aggregating statistics; false otherwise.
*/
public boolean matchesForAggregation(VHTOperation other) {
public boolean matchesForAggregation(VHTOperationElement other) {
// check everything except vhtMcsForNss
return other != null && channel1 == other.channel1 &&
channel2 == other.channel2 && channelWidth == other.channelWidth;
@@ -142,8 +142,8 @@ public class VHTOperation {
if (vhtOper1 == null || vhtOper2 == null) {
return false; // false if exactly one is null
}
VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1);
VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2);
VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1);
VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2);
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
}
@@ -168,7 +168,7 @@ public class VHTOperation {
if (getClass() != obj.getClass()) {
return false;
}
VHTOperation other = (VHTOperation) obj;
VHTOperationElement other = (VHTOperationElement) obj;
return channel1 == other.channel1 && channel2 == other.channel2 &&
channelWidth == other.channelWidth &&
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);

View File

@@ -6,14 +6,14 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
public class DeviceConfiguration {
public static class DeviceConfigurationElement {
public class DeviceConfigurationElement {
public String name;
public String description;
public Integer weight;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
public class DeviceRules {
public String rcOnly;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.ArrayList;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
public class DiGraphEntry {
public String parent;

View File

@@ -6,11 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
public class Entity {
// from ObjectInfo

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;

View File

@@ -6,11 +6,11 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
public class InventoryTag {
// from ObjectInfo

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifi.cloudsdk.models.prov;
package com.facebook.openwifirrm.ucentral.prov.models;
import java.util.List;

Some files were not shown because too many files have changed in this diff Show More