mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-10-29 17:52:24 +00:00
Compare commits
30 Commits
v2.7.0-RC1
...
performanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b0f99c4dd | ||
|
|
e1b9052ecc | ||
|
|
c635af6c1d | ||
|
|
37a904087d | ||
|
|
6df81b7fef | ||
|
|
8171cc74ae | ||
|
|
215d73fee5 | ||
|
|
ecbf8fa644 | ||
|
|
19928e0286 | ||
|
|
da978611d0 | ||
|
|
e42eaa747b | ||
|
|
264f114be2 | ||
|
|
dd2b485b00 | ||
|
|
033d93beff | ||
|
|
e5d5f7d5c0 | ||
|
|
0b4fd49627 | ||
|
|
d81df03637 | ||
|
|
594fd9fa91 | ||
|
|
8c48a8901b | ||
|
|
0ac189f493 | ||
|
|
df21d07ec9 | ||
|
|
01a070c9b7 | ||
|
|
5211eae7c6 | ||
|
|
fafbda0bd8 | ||
|
|
43c9aaafb2 | ||
|
|
89e637cfeb | ||
|
|
0a64fb4963 | ||
|
|
4191bc1a70 | ||
|
|
3b6e83d103 | ||
|
|
27c36ff444 |
2
.github/workflows/deploy-gh-pages.yml
vendored
2
.github/workflows/deploy-gh-pages.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
cache: maven
|
cache: maven
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
run: mvn javadoc:javadoc
|
run: mvn javadoc:aggregate
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,12 +1,9 @@
|
|||||||
/target
|
/target/
|
||||||
/*.log*
|
*/target/
|
||||||
/device_config.json
|
|
||||||
/settings.json
|
|
||||||
/topology.json
|
|
||||||
|
|
||||||
# Eclipse
|
# Eclipse
|
||||||
/.settings/
|
.settings/
|
||||||
/bin/
|
bin/
|
||||||
.metadata
|
.metadata
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
@@ -17,3 +14,6 @@
|
|||||||
*.iml
|
*.iml
|
||||||
*.iws
|
*.iws
|
||||||
*.ipr
|
*.ipr
|
||||||
|
|
||||||
|
# Miscellaneous files thzt should not be checked in
|
||||||
|
temp/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM maven:3-jdk-11 as build
|
FROM maven:3-eclipse-temurin-11 as build
|
||||||
WORKDIR /usr/src/java
|
WORKDIR /usr/src/java
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN mvn clean package -DappendVersionString="$(./scripts/get_build_version.sh)"
|
RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)"
|
||||||
|
|
||||||
FROM adoptopenjdk/openjdk11-openj9:latest
|
FROM adoptopenjdk/openjdk11-openj9:latest
|
||||||
RUN apt-get update && apt-get install -y gettext-base wget
|
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
|
RUN mkdir /owrrm-data
|
||||||
WORKDIR /usr/src/java
|
WORKDIR /usr/src/java
|
||||||
COPY docker-entrypoint.sh /
|
COPY docker-entrypoint.sh /
|
||||||
COPY --from=build /usr/src/java/target/openwifi-rrm.jar /usr/local/bin/
|
COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/
|
||||||
EXPOSE 16789
|
EXPOSE 16789 16790
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \
|
CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \
|
||||||
"-jar", "/usr/local/bin/openwifi-rrm.jar", \
|
"-jar", "/usr/local/bin/openwifi-rrm.jar", \
|
||||||
|
|||||||
43
README.md
43
README.md
@@ -1,12 +1,10 @@
|
|||||||
# OpenWiFi RRM Service
|
# OpenWiFi RRM Service
|
||||||
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
|
[See here](owrrm/README.md) for details.
|
||||||
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,
|
## Project Structure
|
||||||
capabilities) via the uCentral Gateway and Kafka, and integrates with the
|
This is an [Apache Maven] project with the following modules:
|
||||||
OpenWiFi Provisioning service to perform optimization across configured
|
* `lib-cloudsdk` - OpenWiFi CloudSDK Java Library
|
||||||
"venues". It pushes new device configuration parameters to APs after RRM
|
* `owrrm` - OpenWiFi RRM Service
|
||||||
algorithms are run (manually or periodically).
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* **Running:** JRE 11.
|
* **Running:** JRE 11.
|
||||||
@@ -16,7 +14,7 @@ algorithms are run (manually or periodically).
|
|||||||
```
|
```
|
||||||
$ mvn package [-DskipTests]
|
$ mvn package [-DskipTests]
|
||||||
```
|
```
|
||||||
This will build a runnable JAR located at `target/openwifi-rrm.jar`.
|
This will build a runnable JAR located at `owrrm/target/openwifi-rrm.jar`.
|
||||||
|
|
||||||
Alternatively, Docker builds can be launched using the provided
|
Alternatively, Docker builds can be launched using the provided
|
||||||
[Dockerfile](Dockerfile).
|
[Dockerfile](Dockerfile).
|
||||||
@@ -27,34 +25,7 @@ $ mvn test
|
|||||||
```
|
```
|
||||||
Unit tests are written using [JUnit 5].
|
Unit tests are written using [JUnit 5].
|
||||||
|
|
||||||
## Usage
|
## Code Style
|
||||||
```
|
|
||||||
$ 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
|
Code is auto-formatted using [Spotless] with a custom Eclipse style config (see
|
||||||
[spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)).
|
[spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)).
|
||||||
This can be applied via Maven (but is *not* enforced at build time):
|
This can be applied via Maven (but is *not* enforced at build time):
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ services:
|
|||||||
targetPort: 16789
|
targetPort: 16789
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
restapiinternal:
|
restapiinternal:
|
||||||
servicePort: 17007
|
servicePort: 16790
|
||||||
targetPort: 17007
|
targetPort: 16790
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
|
||||||
checks:
|
checks:
|
||||||
|
|||||||
3
lib-cloudsdk/README.md
Normal file
3
lib-cloudsdk/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# OpenWiFi CloudSDK Java Library
|
||||||
|
A Java library providing clients and models for the OpenWiFi uCentral-based
|
||||||
|
CloudSDK.
|
||||||
80
lib-cloudsdk/pom.xml
Normal file
80
lib-cloudsdk/pom.xml
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,14 +6,16 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,16 +23,16 @@ import com.google.gson.JsonObject;
|
|||||||
*/
|
*/
|
||||||
public class UCentralApConfiguration {
|
public class UCentralApConfiguration {
|
||||||
/** The raw configuration. */
|
/** The raw configuration. */
|
||||||
private final JsonObject config;
|
private final UCentralSchema config;
|
||||||
|
|
||||||
/** Constructor from JSON string. */
|
/** Constructor from JSON string. */
|
||||||
public UCentralApConfiguration(String configJson) {
|
public UCentralApConfiguration(String configJson) {
|
||||||
this.config = new Gson().fromJson(configJson, JsonObject.class);
|
this.config = new Gson().fromJson(configJson, UCentralSchema.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor from JsonObject (makes deep copy). */
|
/** Constructor from JsonObject */
|
||||||
public UCentralApConfiguration(JsonObject config) {
|
public UCentralApConfiguration(JsonObject config) {
|
||||||
this.config = config.deepCopy();
|
this.config = new Gson().fromJson(config, UCentralSchema.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,22 +47,21 @@ public class UCentralApConfiguration {
|
|||||||
|
|
||||||
/** Return the number of radios, or -1 if the field is missing/malformed. */
|
/** Return the number of radios, or -1 if the field is missing/malformed. */
|
||||||
public int getRadioCount() {
|
public int getRadioCount() {
|
||||||
if (!config.has("radios") || !config.get("radios").isJsonArray()) {
|
if (config.radios == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return config.getAsJsonArray("radios").size();
|
return config.radios.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return all info in the radio config (or an empty array if none). */
|
/** Return all info in the radio config (or an empty array if none). */
|
||||||
public JsonArray getRadioConfigList() {
|
public List<UCentralSchema.Radio> getRadioConfigList() {
|
||||||
if (!config.has("radios") || !config.get("radios").isJsonArray()) {
|
return config.radios;
|
||||||
return new JsonArray();
|
|
||||||
}
|
|
||||||
return config.getAsJsonArray("radios");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return all the operational bands of an AP (from the radio config) */
|
/** Return all the operational bands of an AP (from the radio config) */
|
||||||
public Set<String> getRadioBandsSet(JsonArray radioConfigList) {
|
public Set<String> getRadioBandsSet(
|
||||||
|
List<UCentralSchema.Radio> radioConfigList
|
||||||
|
) {
|
||||||
Set<String> radioBandsSet = new HashSet<>();
|
Set<String> radioBandsSet = new HashSet<>();
|
||||||
if (radioConfigList == null) {
|
if (radioConfigList == null) {
|
||||||
return radioBandsSet;
|
return radioBandsSet;
|
||||||
@@ -69,46 +70,39 @@ public class UCentralApConfiguration {
|
|||||||
int radioIndex = 0; radioIndex < radioConfigList.size();
|
int radioIndex = 0; radioIndex < radioConfigList.size();
|
||||||
radioIndex++
|
radioIndex++
|
||||||
) {
|
) {
|
||||||
JsonElement e = radioConfigList.get(radioIndex);
|
UCentralSchema.Radio radio = radioConfigList.get(radioIndex);
|
||||||
if (!e.isJsonObject()) {
|
if (radio == null || radio.band == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
JsonObject radioObject = e.getAsJsonObject();
|
radioBandsSet.add(radio.band);
|
||||||
if (!radioObject.has("band")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
radioBandsSet.add(radioObject.get("band").getAsString());
|
|
||||||
}
|
}
|
||||||
return radioBandsSet;
|
return radioBandsSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the radio config at the given index, or null if invalid. */
|
/** Return the radio config at the given index, or null if invalid. */
|
||||||
public JsonObject getRadioConfig(int index) {
|
public UCentralSchema.Radio getRadioConfig(int index) {
|
||||||
if (getRadioCount() < index) {
|
if (getRadioCount() < index) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
JsonArray radios = config.getAsJsonArray("radios");
|
List<UCentralSchema.Radio> radios = config.radios;
|
||||||
if (radios == null) {
|
if (radios == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
JsonElement e = radios.get(index);
|
UCentralSchema.Radio radio = radios.get(index);
|
||||||
if (!e.isJsonObject()) {
|
return radio;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return e.getAsJsonObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set radio config at the given index. Adds empty objects as needed. */
|
/** Set radio config at the given index. Adds empty objects as needed. */
|
||||||
public void setRadioConfig(int index, JsonObject radioConfig) {
|
public void setRadioConfig(int index, UCentralSchema.Radio radioConfig) {
|
||||||
int radioCount = getRadioCount();
|
int radioCount = getRadioCount();
|
||||||
if (radioCount == -1) {
|
if (radioCount == -1) {
|
||||||
config.add("radios", new JsonArray());
|
config.radios = new ArrayList<UCentralSchema.Radio>();
|
||||||
radioCount = 0;
|
radioCount = 0;
|
||||||
}
|
}
|
||||||
JsonArray radios = config.getAsJsonArray("radios");
|
List<UCentralSchema.Radio> radios = config.radios;
|
||||||
for (int i = radioCount; i <= index; i++) {
|
for (int i = radioCount; i <= index; i++) {
|
||||||
// insert empty objects as needed
|
// insert empty objects as needed
|
||||||
radios.add(new JsonObject());
|
radios.add(new UCentralSchema.Radio());
|
||||||
}
|
}
|
||||||
radios.set(index, radioConfig);
|
radios.set(index, radioConfig);
|
||||||
}
|
}
|
||||||
@@ -119,11 +113,7 @@ public class UCentralApConfiguration {
|
|||||||
*/
|
*/
|
||||||
public int getStatisticsInterval() {
|
public int getStatisticsInterval() {
|
||||||
try {
|
try {
|
||||||
return config
|
return config.metrics.statistics.interval;
|
||||||
.getAsJsonObject("metrics")
|
|
||||||
.getAsJsonObject("statistics")
|
|
||||||
.get("interval")
|
|
||||||
.getAsInt();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -131,17 +121,14 @@ public class UCentralApConfiguration {
|
|||||||
|
|
||||||
/** Set the statistics interval to the given value (in seconds). */
|
/** Set the statistics interval to the given value (in seconds). */
|
||||||
public void setStatisticsInterval(int intervalSec) {
|
public void setStatisticsInterval(int intervalSec) {
|
||||||
if (!config.has("metrics") || !config.get("metrics").isJsonObject()) {
|
if (config.metrics == null) {
|
||||||
config.add("metrics", new JsonObject());
|
config.metrics = new UCentralSchema.Metrics();
|
||||||
}
|
}
|
||||||
JsonObject metrics = config.getAsJsonObject("metrics");
|
|
||||||
if (
|
if (
|
||||||
!metrics.has("statistics") ||
|
config.metrics.statistics == null
|
||||||
!metrics.get("statistics").isJsonObject()
|
|
||||||
) {
|
) {
|
||||||
metrics.add("statistics", new JsonObject());
|
config.metrics.statistics = new UCentralSchema.Metrics.Statistics();
|
||||||
}
|
}
|
||||||
JsonObject statistics = metrics.getAsJsonObject("statistics");
|
config.metrics.statistics.interval = intervalSec;
|
||||||
statistics.addProperty("interval", intervalSec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -20,22 +20,21 @@ import org.json.JSONObject;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.RRMConfig.UCentralConfig.UCentralSocketParams;
|
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceConfigureRequest;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceListWithStatus;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
|
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
|
import com.facebook.openwifi.cloudsdk.models.gw.WifiScanRequest;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest;
|
import com.facebook.openwifi.cloudsdk.models.prov.EntityList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
|
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
|
import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails;
|
import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList;
|
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
@@ -127,8 +126,14 @@ public class UCentralClient {
|
|||||||
/** uCentral password */
|
/** uCentral password */
|
||||||
private final String password;
|
private final String password;
|
||||||
|
|
||||||
/** Socket parameters */
|
/** Connection timeout for all requests, in ms */
|
||||||
private final UCentralSocketParams socketParams;
|
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;
|
||||||
|
|
||||||
/** The learned service endpoints. */
|
/** The learned service endpoints. */
|
||||||
private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>();
|
private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>();
|
||||||
@@ -147,7 +152,9 @@ public class UCentralClient {
|
|||||||
* (if needed)
|
* (if needed)
|
||||||
* @param username uCentral username (for public endpoints only)
|
* @param username uCentral username (for public endpoints only)
|
||||||
* @param password uCentral password (for public endpoints only)
|
* @param password uCentral password (for public endpoints only)
|
||||||
* @param socketParams Socket parameters
|
* @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
|
||||||
*/
|
*/
|
||||||
public UCentralClient(
|
public UCentralClient(
|
||||||
String rrmEndpoint,
|
String rrmEndpoint,
|
||||||
@@ -155,13 +162,17 @@ public class UCentralClient {
|
|||||||
String uCentralSecPublicEndpoint,
|
String uCentralSecPublicEndpoint,
|
||||||
String username,
|
String username,
|
||||||
String password,
|
String password,
|
||||||
UCentralSocketParams socketParams
|
int connectTimeoutMs,
|
||||||
|
int socketTimeoutMs,
|
||||||
|
int wifiScanTimeoutMs
|
||||||
) {
|
) {
|
||||||
this.rrmEndpoint = rrmEndpoint;
|
this.rrmEndpoint = rrmEndpoint;
|
||||||
this.usePublicEndpoints = usePublicEndpoints;
|
this.usePublicEndpoints = usePublicEndpoints;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.socketParams = socketParams;
|
this.connectTimeoutMs = connectTimeoutMs;
|
||||||
|
this.socketTimeoutMs = socketTimeoutMs;
|
||||||
|
this.wifiScanTimeoutMs = wifiScanTimeoutMs;
|
||||||
|
|
||||||
if (usePublicEndpoints) {
|
if (usePublicEndpoints) {
|
||||||
setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint);
|
setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint);
|
||||||
@@ -305,8 +316,8 @@ public class UCentralClient {
|
|||||||
endpoint,
|
endpoint,
|
||||||
service,
|
service,
|
||||||
parameters,
|
parameters,
|
||||||
socketParams.connectTimeoutMs,
|
connectTimeoutMs,
|
||||||
socketParams.socketTimeoutMs
|
socketTimeoutMs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,8 +360,8 @@ public class UCentralClient {
|
|||||||
endpoint,
|
endpoint,
|
||||||
service,
|
service,
|
||||||
body,
|
body,
|
||||||
socketParams.connectTimeoutMs,
|
connectTimeoutMs,
|
||||||
socketParams.socketTimeoutMs
|
socketTimeoutMs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,8 +465,8 @@ public class UCentralClient {
|
|||||||
String.format("device/%s/wifiscan", serialNumber),
|
String.format("device/%s/wifiscan", serialNumber),
|
||||||
OWGW_SERVICE,
|
OWGW_SERVICE,
|
||||||
req,
|
req,
|
||||||
socketParams.connectTimeoutMs,
|
connectTimeoutMs,
|
||||||
socketParams.wifiScanTimeoutMs
|
wifiScanTimeoutMs
|
||||||
);
|
);
|
||||||
if (!response.isSuccess()) {
|
if (!response.isSuccess()) {
|
||||||
logger.error("Error: {}", response.getBody());
|
logger.error("Error: {}", response.getBody());
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -22,7 +22,7 @@ public final class UCentralConstants {
|
|||||||
public static final String BAND_2G = "2G";
|
public static final String BAND_2G = "2G";
|
||||||
/** String of the 5 GHz band */
|
/** String of the 5 GHz band */
|
||||||
public static final String BAND_5G = "5G";
|
public static final String BAND_5G = "5G";
|
||||||
/** List of all bands */
|
/** List of all bands ordered from lowest to highest */
|
||||||
public static final List<String> BANDS = Collections
|
public static final List<String> BANDS = Collections
|
||||||
.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G));
|
.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G));
|
||||||
|
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* 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.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||||
|
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.openwifi.cloudsdk.models.ap.UCentralSchema;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uCentral utility methods/structures.
|
||||||
|
*/
|
||||||
|
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());
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
*
|
||||||
|
* @param result result of the wifiscan
|
||||||
|
* @param timestampMs Unix time in ms
|
||||||
|
* @return list of wifiscan entries, or null if any parsing/deserialization
|
||||||
|
* error occurred.
|
||||||
|
*/
|
||||||
|
public static List<WifiScanEntry> parseWifiScanEntries(
|
||||||
|
JsonObject result,
|
||||||
|
long timestampMs
|
||||||
|
) {
|
||||||
|
List<WifiScanEntry> entries = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
JsonArray scanInfo = result
|
||||||
|
.getAsJsonObject("status")
|
||||||
|
.getAsJsonArray("scan");
|
||||||
|
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 channel of an AP to a given value.
|
||||||
|
*
|
||||||
|
* Returns true if changed, or false if unchanged for any reason.
|
||||||
|
*/
|
||||||
|
public static boolean setRadioConfigChannel(
|
||||||
|
String serialNumber,
|
||||||
|
UCentralApConfiguration config,
|
||||||
|
Map<String, Integer> newValueList
|
||||||
|
) {
|
||||||
|
return setRadioConfigField(
|
||||||
|
serialNumber,
|
||||||
|
config,
|
||||||
|
"channel",
|
||||||
|
newValueList
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all radios config tx power of an AP to a given value.
|
||||||
|
*
|
||||||
|
* Returns true if changed, or false if unchanged for any reason.
|
||||||
|
*/
|
||||||
|
public static boolean setRadioConfigTxPower(
|
||||||
|
String serialNumber,
|
||||||
|
UCentralApConfiguration config,
|
||||||
|
Map<String, Integer> newValueList
|
||||||
|
) {
|
||||||
|
return setRadioConfigField(
|
||||||
|
serialNumber,
|
||||||
|
config,
|
||||||
|
"tx-power",
|
||||||
|
newValueList
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all radios config of an AP to a given value.
|
||||||
|
*
|
||||||
|
* Returns true if changed, or false if unchanged for any reason.
|
||||||
|
*/
|
||||||
|
private static boolean setRadioConfigField(
|
||||||
|
String serialNumber,
|
||||||
|
UCentralApConfiguration config,
|
||||||
|
String fieldName,
|
||||||
|
Map<String, Integer> newValueList
|
||||||
|
) {
|
||||||
|
boolean wasModified = false;
|
||||||
|
|
||||||
|
// Iterate all the radios of an AP to find the corresponding band
|
||||||
|
for (
|
||||||
|
int radioIndex = 0; radioIndex < config.getRadioCount();
|
||||||
|
radioIndex++
|
||||||
|
) {
|
||||||
|
UCentralSchema.Radio radioConfig =
|
||||||
|
config.getRadioConfig(radioIndex);
|
||||||
|
if (radioConfig == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String operationalBand = radioConfig.band;
|
||||||
|
if (!newValueList.containsKey(operationalBand)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the fieldName doesn't exist in config, we generate the fieldName and
|
||||||
|
// assign the new value to it.
|
||||||
|
int newValue = newValueList.get(operationalBand);
|
||||||
|
Integer currentValue = null;
|
||||||
|
|
||||||
|
switch (fieldName) {
|
||||||
|
case "channel":
|
||||||
|
if (
|
||||||
|
radioConfig.channel == null ||
|
||||||
|
radioConfig.channel.isString()
|
||||||
|
) {
|
||||||
|
wasModified = true;
|
||||||
|
} else {
|
||||||
|
currentValue = radioConfig.channel.getAsInt();
|
||||||
|
|
||||||
|
if (currentValue != newValue) {
|
||||||
|
wasModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
radioConfig.channel = new JsonPrimitive(newValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "tx-power":
|
||||||
|
currentValue = radioConfig.txPower;
|
||||||
|
if (currentValue != newValue) {
|
||||||
|
radioConfig.txPower = newValue;
|
||||||
|
wasModified = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasModified) {
|
||||||
|
logger.info(
|
||||||
|
"Device {}: setting {} {} to {} (was {})",
|
||||||
|
serialNumber,
|
||||||
|
operationalBand,
|
||||||
|
fieldName,
|
||||||
|
newValue,
|
||||||
|
currentValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentValue != null && currentValue == newValue) {
|
||||||
|
logger.info(
|
||||||
|
"Device {}: {} {} is already {}",
|
||||||
|
serialNumber,
|
||||||
|
operationalBand,
|
||||||
|
fieldName,
|
||||||
|
newValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wasModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the APs on a band who participate in an optimization algorithm.
|
||||||
|
* Get the info from the configuration field in deviceStatus
|
||||||
|
* (Since the State doesn't explicitly show the "band" info)
|
||||||
|
*
|
||||||
|
* Returns the results map
|
||||||
|
*/
|
||||||
|
public static Map<String, List<String>> getBandsMap(
|
||||||
|
Map<String, List<UCentralSchema.Radio>> deviceStatus
|
||||||
|
) {
|
||||||
|
Map<String, List<String>> bandsMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (String serialNumber : deviceStatus.keySet()) {
|
||||||
|
List<UCentralSchema.Radio> radioList =
|
||||||
|
deviceStatus.get(serialNumber);
|
||||||
|
for (
|
||||||
|
int radioIndex = 0; radioIndex < radioList.size(); radioIndex++
|
||||||
|
) {
|
||||||
|
UCentralSchema.Radio radio = radioList.get(radioIndex);
|
||||||
|
String band = radio.band;
|
||||||
|
bandsMap
|
||||||
|
.computeIfAbsent(band, k -> new ArrayList<>())
|
||||||
|
.add(serialNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bandsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the capabilities of the APs who participate in an optimization algorithm.
|
||||||
|
*
|
||||||
|
* @param deviceStatus map of {device, status}
|
||||||
|
* @param deviceCapabilities map of {device, capabilities info}
|
||||||
|
* @param defaultAvailableChannels map of {band, list of available channels}
|
||||||
|
*
|
||||||
|
* @return the results map of {band, {device, list of available channels}}
|
||||||
|
*/
|
||||||
|
public static Map<String, Map<String, List<Integer>>> getDeviceAvailableChannels(
|
||||||
|
Map<String, List<UCentralSchema.Radio>> deviceStatus,
|
||||||
|
Map<String, Map<String, Capabilities.Phy>> deviceCapabilities,
|
||||||
|
Map<String, List<Integer>> defaultAvailableChannels
|
||||||
|
) {
|
||||||
|
Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
|
||||||
|
new HashMap<>();
|
||||||
|
|
||||||
|
for (String serialNumber : deviceStatus.keySet()) {
|
||||||
|
List<UCentralSchema.Radio> radioList =
|
||||||
|
deviceStatus.get(serialNumber);
|
||||||
|
for (
|
||||||
|
int radioIndex = 0; radioIndex < radioList.size(); radioIndex++
|
||||||
|
) {
|
||||||
|
UCentralSchema.Radio radio = radioList.get(radioIndex);
|
||||||
|
String band = radio.band;
|
||||||
|
Map<String, Capabilities.Phy> capabilitiesPhyMap =
|
||||||
|
deviceCapabilities.get(serialNumber);
|
||||||
|
List<Integer> availableChannels = new ArrayList<>();
|
||||||
|
if (capabilitiesPhyMap == null) {
|
||||||
|
availableChannels
|
||||||
|
.addAll(defaultAvailableChannels.get(band));
|
||||||
|
} else {
|
||||||
|
Set<Entry<String, Capabilities.Phy>> entrySet =
|
||||||
|
capabilitiesPhyMap
|
||||||
|
.entrySet();
|
||||||
|
for (Map.Entry<String, Capabilities.Phy> f : entrySet) {
|
||||||
|
Capabilities.Phy phy = f.getValue();
|
||||||
|
String bandInsideObject = phy.band.toString();
|
||||||
|
if (bandInsideObject.equals(band)) {
|
||||||
|
// (TODO) Remove the following dfsChannels code block
|
||||||
|
// when the DFS channels are available
|
||||||
|
Set<Integer> dfsChannels = new HashSet<>();
|
||||||
|
try {
|
||||||
|
int[] channelInfo = phy.dfs_channels;
|
||||||
|
for (int d : channelInfo) {
|
||||||
|
dfsChannels.add(d);
|
||||||
|
}
|
||||||
|
} catch (Exception d) {}
|
||||||
|
try {
|
||||||
|
int[] channelInfo = phy.channels;
|
||||||
|
for (int channel : channelInfo) {
|
||||||
|
if (!dfsChannels.contains(channel)) {
|
||||||
|
availableChannels.add(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception c) {
|
||||||
|
availableChannels
|
||||||
|
.addAll(defaultAvailableChannels.get(band));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceAvailableChannels.computeIfAbsent(
|
||||||
|
band,
|
||||||
|
k -> new HashMap<>()
|
||||||
|
)
|
||||||
|
.put(
|
||||||
|
serialNumber,
|
||||||
|
availableChannels
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceAvailableChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the mapping between bssids and APs.
|
||||||
|
* Get the info from the State data
|
||||||
|
*
|
||||||
|
* Returns the results map
|
||||||
|
*/
|
||||||
|
public static Map<String, String> getBssidsMap(
|
||||||
|
Map<String, State> latestState
|
||||||
|
) {
|
||||||
|
Map<String, String> bssidMap = new HashMap<>();
|
||||||
|
for (Map.Entry<String, State> e : latestState.entrySet()) {
|
||||||
|
State state = e.getValue();
|
||||||
|
for (
|
||||||
|
int interfaceIndex = 0;
|
||||||
|
interfaceIndex < state.interfaces.length;
|
||||||
|
interfaceIndex++
|
||||||
|
) {
|
||||||
|
if (state.interfaces[interfaceIndex].ssids == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
int ssidIndex = 0;
|
||||||
|
ssidIndex < state.interfaces[interfaceIndex].ssids.length;
|
||||||
|
ssidIndex++
|
||||||
|
) {
|
||||||
|
bssidMap.put(
|
||||||
|
state.interfaces[interfaceIndex].ssids[ssidIndex].bssid,
|
||||||
|
e.getKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bssidMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the given channel is in the given band.
|
||||||
|
*
|
||||||
|
* @param channel channel number
|
||||||
|
* @param band "2G" or "5G"
|
||||||
|
* @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 which band contains the given frequency (MHz). */
|
||||||
|
public static String freqToBand(int freqMHz) {
|
||||||
|
if (2412 <= freqMHz && freqMHz <= 2484) {
|
||||||
|
return "2G";
|
||||||
|
} else {
|
||||||
|
return "5G";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to parse channel width, if it encounters an error it will return null.
|
||||||
|
* It can handle 80p80 in two ways. First it can just treat it as 160. Second,
|
||||||
|
* it can just apply to the first 80 channel and ignore the second. This is
|
||||||
|
* controlled by treatSeparate.
|
||||||
|
*
|
||||||
|
* @param channelWidthStr the channel width
|
||||||
|
* @param treatSeparate treats each band separately
|
||||||
|
* @return channel width in MHz
|
||||||
|
*/
|
||||||
|
public static Integer parseChannelWidth(
|
||||||
|
String channelWidthStr,
|
||||||
|
boolean treatSeparate
|
||||||
|
) {
|
||||||
|
// 80p80 is the only case where it can't be parsed into an integer
|
||||||
|
if (channelWidthStr.equals("80p80")) {
|
||||||
|
return treatSeparate ? 80 : 160;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(channelWidthStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to parse the index from the reference string in the JSON returned from
|
||||||
|
* other services. Note that this only returns the index, the caller is
|
||||||
|
* responsible for making sure that the correct field is passed in and the
|
||||||
|
* index is used in the correct fields. If there's an error parsing, it will
|
||||||
|
* return null.
|
||||||
|
*
|
||||||
|
* @param reference The reference string, keyed under "$ref"
|
||||||
|
* @return the index of the reference or null if an error occurred.
|
||||||
|
*/
|
||||||
|
public static Integer parseReferenceIndex(String reference) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(
|
||||||
|
reference,
|
||||||
|
reference.lastIndexOf("/") + 1,
|
||||||
|
reference.length(),
|
||||||
|
10
|
||||||
|
);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.models.WifiScanEntryResult;
|
import com.facebook.openwifi.cloudsdk.models.ap.WifiScanEntryResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends {@link WifiScanEntryResult} to track the response time of the entry.
|
* Extends {@link WifiScanEntryResult} to track the response time of the entry.
|
||||||
@@ -22,6 +22,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
* time reference.
|
* time reference.
|
||||||
*/
|
*/
|
||||||
public long unixTimeMs;
|
public long unixTimeMs;
|
||||||
|
/** Stores Information Elements (IEs) from the wifiscan entry. */
|
||||||
|
public InformationElements ieContainer;
|
||||||
|
|
||||||
/** Default Constructor. */
|
/** Default Constructor. */
|
||||||
public WifiScanEntry() {}
|
public WifiScanEntry() {}
|
||||||
@@ -30,13 +32,14 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
public WifiScanEntry(WifiScanEntry o) {
|
public WifiScanEntry(WifiScanEntry o) {
|
||||||
super(o);
|
super(o);
|
||||||
this.unixTimeMs = o.unixTimeMs;
|
this.unixTimeMs = o.unixTimeMs;
|
||||||
|
this.ieContainer = o.ieContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = super.hashCode();
|
int result = super.hashCode();
|
||||||
result = prime * result + Objects.hash(unixTimeMs);
|
result = prime * result + Objects.hash(ieContainer, unixTimeMs);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +55,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WifiScanEntry other = (WifiScanEntry) obj;
|
WifiScanEntry other = (WifiScanEntry) obj;
|
||||||
return unixTimeMs == other.unixTimeMs;
|
return Objects.equals(ieContainer, other.ieContainer) &&
|
||||||
|
unixTimeMs == other.unixTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
package com.facebook.openwifi.cloudsdk.ies;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
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
|
* High Throughput (HT) Operation Element, which is potentially present in
|
||||||
* wifiscan entries. Introduced in 802.11n (2009).
|
* wifiscan entries. Introduced in 802.11n (2009).
|
||||||
*/
|
*/
|
||||||
public class HTOperationElement {
|
public class HTOperation {
|
||||||
|
|
||||||
/** Channel number of the primary channel. */
|
/** Channel number of the primary channel. */
|
||||||
public final byte primaryChannel;
|
public final byte primaryChannel;
|
||||||
@@ -78,7 +78,7 @@ public class HTOperationElement {
|
|||||||
* For details about the parameters, see the javadocs for the corresponding
|
* For details about the parameters, see the javadocs for the corresponding
|
||||||
* member variables.
|
* member variables.
|
||||||
*/
|
*/
|
||||||
public HTOperationElement(
|
public HTOperation(
|
||||||
byte primaryChannel,
|
byte primaryChannel,
|
||||||
byte secondaryChannelOffset,
|
byte secondaryChannelOffset,
|
||||||
boolean staChannelWidth,
|
boolean staChannelWidth,
|
||||||
@@ -114,7 +114,7 @@ public class HTOperationElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor with the most used parameters. */
|
/** Constructor with the most used parameters. */
|
||||||
public HTOperationElement(
|
public HTOperation(
|
||||||
byte primaryChannel,
|
byte primaryChannel,
|
||||||
byte secondaryChannelOffset,
|
byte secondaryChannelOffset,
|
||||||
boolean staChannelWidth,
|
boolean staChannelWidth,
|
||||||
@@ -141,7 +141,7 @@ public class HTOperationElement {
|
|||||||
* @param htOper a base64 encoded properly formatted HT operation element (see
|
* @param htOper a base64 encoded properly formatted HT operation element (see
|
||||||
* 802.11)
|
* 802.11)
|
||||||
*/
|
*/
|
||||||
public HTOperationElement(String htOper) {
|
public HTOperation(String htOper) {
|
||||||
byte[] bytes = Base64.decodeBase64(htOper);
|
byte[] bytes = Base64.decodeBase64(htOper);
|
||||||
/*
|
/*
|
||||||
* Note that the code here may seem to read "reversed" compared to 802.11. This
|
* Note that the code here may seem to read "reversed" compared to 802.11. This
|
||||||
@@ -182,7 +182,7 @@ public class HTOperationElement {
|
|||||||
* @return true if the the operation elements "match" for the purpose of
|
* @return true if the the operation elements "match" for the purpose of
|
||||||
* aggregating statistics; false otherwise.
|
* aggregating statistics; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean matchesForAggregation(HTOperationElement other) {
|
public boolean matchesForAggregation(HTOperation other) {
|
||||||
return other != null && primaryChannel == other.primaryChannel &&
|
return other != null && primaryChannel == other.primaryChannel &&
|
||||||
secondaryChannelOffset == other.secondaryChannelOffset &&
|
secondaryChannelOffset == other.secondaryChannelOffset &&
|
||||||
staChannelWidth == other.staChannelWidth &&
|
staChannelWidth == other.staChannelWidth &&
|
||||||
@@ -211,8 +211,8 @@ public class HTOperationElement {
|
|||||||
if (htOper1 == null || htOper2 == null) {
|
if (htOper1 == null || htOper2 == null) {
|
||||||
return false; // false if exactly one is null
|
return false; // false if exactly one is null
|
||||||
}
|
}
|
||||||
HTOperationElement htOperObj1 = new HTOperationElement(htOper1);
|
HTOperation htOperObj1 = new HTOperation(htOper1);
|
||||||
HTOperationElement htOperObj2 = new HTOperationElement(htOper2);
|
HTOperation htOperObj2 = new HTOperation(htOper2);
|
||||||
return htOperObj1.matchesForAggregation(htOperObj2);
|
return htOperObj1.matchesForAggregation(htOperObj2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ public class HTOperationElement {
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HTOperationElement other = (HTOperationElement) obj;
|
HTOperation other = (HTOperation) obj;
|
||||||
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
|
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
|
||||||
channelCenterFrequencySegment2 ==
|
channelCenterFrequencySegment2 ==
|
||||||
other.channelCenterFrequencySegment2 &&
|
other.channelCenterFrequencySegment2 &&
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* 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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
package com.facebook.openwifi.cloudsdk.ies;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
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
|
* Very High Throughput (VHT) Operation Element, which is potentially present in
|
||||||
* wifiscan entries. Introduced in 802.11ac (2013).
|
* wifiscan entries. Introduced in 802.11ac (2013).
|
||||||
*/
|
*/
|
||||||
public class VHTOperationElement {
|
public class VHTOperation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise.
|
* This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise.
|
||||||
@@ -30,15 +30,23 @@ public class VHTOperationElement {
|
|||||||
* 160 MHz wide channel, this parameter is the channel number of the 80MHz
|
* 160 MHz wide channel, this parameter is the channel number of the 80MHz
|
||||||
* channel that contains the primary channel. For a 80+80 MHz wide channel, this
|
* channel that contains the primary channel. For a 80+80 MHz wide channel, this
|
||||||
* parameter is the channel number of the primary channel.
|
* parameter is the channel number of the primary channel.
|
||||||
|
* <p>
|
||||||
|
* This field is an unsigned byte in the specification (i.e., with values
|
||||||
|
* between 0 and 255). But because Java only supports signed bytes, a short
|
||||||
|
* data type is used to store the value.
|
||||||
*/
|
*/
|
||||||
public final byte channel1;
|
public final short channel1;
|
||||||
/**
|
/**
|
||||||
* This should be zero unless the channel is 160MHz or 80+80 MHz wide. If the
|
* This should be zero unless the channel is 160MHz or 80+80 MHz wide. If the
|
||||||
* channel is 160 MHz wide, this parameter is the channel number of the 160 MHz
|
* channel is 160 MHz wide, this parameter is the channel number of the 160 MHz
|
||||||
* wide channel. If the channel is 80+80 MHz wide, this parameter is the channel
|
* wide channel. If the channel is 80+80 MHz wide, this parameter is the channel
|
||||||
* index of the secondary 80 MHz wide channel.
|
* index of the secondary 80 MHz wide channel.
|
||||||
|
* <p>
|
||||||
|
* This field is an unsigned byte in the specification (i.e., with values
|
||||||
|
* between 0 and 255). But because Java only supports signed bytes, a short
|
||||||
|
* data type is used to store the value.
|
||||||
*/
|
*/
|
||||||
public final byte channel2;
|
public final short channel2;
|
||||||
/**
|
/**
|
||||||
* An 8-element array where each element is between 0 and 4 inclusive. MCS means
|
* An 8-element array where each element is between 0 and 4 inclusive. MCS means
|
||||||
* Modulation and Coding Scheme. NSS means Number of Spatial Streams. There can
|
* Modulation and Coding Scheme. NSS means Number of Spatial Streams. There can
|
||||||
@@ -57,11 +65,11 @@ public class VHTOperationElement {
|
|||||||
* @param vhtOper a base64 encoded properly formatted VHT operation element (see
|
* @param vhtOper a base64 encoded properly formatted VHT operation element (see
|
||||||
* 802.11 standard)
|
* 802.11 standard)
|
||||||
*/
|
*/
|
||||||
public VHTOperationElement(String vhtOper) {
|
public VHTOperation(String vhtOper) {
|
||||||
byte[] bytes = Base64.decodeBase64(vhtOper);
|
byte[] bytes = Base64.decodeBase64(vhtOper);
|
||||||
this.channelWidth = bytes[0];
|
this.channelWidth = bytes[0];
|
||||||
this.channel1 = bytes[1];
|
this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
|
||||||
this.channel2 = bytes[2];
|
this.channel2 = (short) (bytes[2] & 0xff); // read as unsigned value
|
||||||
byte[] vhtMcsForNss = new byte[8];
|
byte[] vhtMcsForNss = new byte[8];
|
||||||
vhtMcsForNss[0] = (byte) (bytes[3] >>> 6);
|
vhtMcsForNss[0] = (byte) (bytes[3] >>> 6);
|
||||||
vhtMcsForNss[1] = (byte) ((bytes[3] & 0b00110000) >>> 4);
|
vhtMcsForNss[1] = (byte) ((bytes[3] & 0b00110000) >>> 4);
|
||||||
@@ -81,10 +89,10 @@ public class VHTOperationElement {
|
|||||||
* For details about the parameters, see the javadocs for the corresponding
|
* For details about the parameters, see the javadocs for the corresponding
|
||||||
* member variables.
|
* member variables.
|
||||||
*/
|
*/
|
||||||
public VHTOperationElement(
|
public VHTOperation(
|
||||||
byte channelWidth,
|
byte channelWidth,
|
||||||
byte channel1,
|
short channel1,
|
||||||
byte channel2,
|
short channel2,
|
||||||
byte[] vhtMcsForNss
|
byte[] vhtMcsForNss
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
@@ -106,7 +114,7 @@ public class VHTOperationElement {
|
|||||||
* @return true if the the operation elements "match" for the purpose of
|
* @return true if the the operation elements "match" for the purpose of
|
||||||
* aggregating statistics; false otherwise.
|
* aggregating statistics; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean matchesForAggregation(VHTOperationElement other) {
|
public boolean matchesForAggregation(VHTOperation other) {
|
||||||
// check everything except vhtMcsForNss
|
// check everything except vhtMcsForNss
|
||||||
return other != null && channel1 == other.channel1 &&
|
return other != null && channel1 == other.channel1 &&
|
||||||
channel2 == other.channel2 && channelWidth == other.channelWidth;
|
channel2 == other.channel2 && channelWidth == other.channelWidth;
|
||||||
@@ -134,8 +142,8 @@ public class VHTOperationElement {
|
|||||||
if (vhtOper1 == null || vhtOper2 == null) {
|
if (vhtOper1 == null || vhtOper2 == null) {
|
||||||
return false; // false if exactly one is null
|
return false; // false if exactly one is null
|
||||||
}
|
}
|
||||||
VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1);
|
VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1);
|
||||||
VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2);
|
VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2);
|
||||||
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
|
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +168,7 @@ public class VHTOperationElement {
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
VHTOperationElement other = (VHTOperationElement) obj;
|
VHTOperation other = (VHTOperation) obj;
|
||||||
return channel1 == other.channel1 && channel2 == other.channel2 &&
|
return channel1 == other.channel1 && channel2 == other.channel2 &&
|
||||||
channelWidth == other.channelWidth &&
|
channelWidth == other.channelWidth &&
|
||||||
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
|
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk.kafka;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.apache.kafka.common.errors.WakeupException;
|
import org.apache.kafka.common.errors.WakeupException;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kafka runner.
|
* Kafka runner.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk.kafka;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -29,7 +29,8 @@ import org.apache.kafka.common.serialization.StringDeserializer;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral;
|
package com.facebook.openwifi.cloudsdk.kafka;
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ import org.apache.kafka.common.serialization.StringSerializer;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.ap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class Capabilities {
|
||||||
|
public String compatible;
|
||||||
|
public String model;
|
||||||
|
public String platform;
|
||||||
|
public Map<String, List<String>> network;
|
||||||
|
|
||||||
|
public static class Switch {
|
||||||
|
public boolean enable;
|
||||||
|
public boolean reset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("switch") public Map<String, Switch> switch_;
|
||||||
|
|
||||||
|
public static class Phy {
|
||||||
|
public int tx_ant;
|
||||||
|
public int rx_ant;
|
||||||
|
public int[] frequencies;
|
||||||
|
public int[] channels;
|
||||||
|
public int[] dfs_channels;
|
||||||
|
public String[] htmode;
|
||||||
|
public String[] band;
|
||||||
|
public int ht_capa;
|
||||||
|
public int vht_capa;
|
||||||
|
public int[] he_phy_capa;
|
||||||
|
public int[] he_mac_capa;
|
||||||
|
public String country;
|
||||||
|
public String dfs_region;
|
||||||
|
public int temperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Phy> wifi;
|
||||||
|
// TODO The fields below were omitted
|
||||||
|
// macaddr;
|
||||||
|
// country_code;
|
||||||
|
// label_macaddr;
|
||||||
|
}
|
||||||
@@ -6,14 +6,14 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.models;
|
package com.facebook.openwifi.cloudsdk.models.ap;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
public class State {
|
public class State {
|
||||||
public class Interface {
|
public static class Interface {
|
||||||
public class Client {
|
public static class Client {
|
||||||
public String mac;
|
public String mac;
|
||||||
public String[] ipv4_addresses;
|
public String[] ipv4_addresses;
|
||||||
public String[] ipv6_addresses;
|
public String[] ipv6_addresses;
|
||||||
@@ -21,9 +21,9 @@ public class State {
|
|||||||
// TODO last_seen
|
// TODO last_seen
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SSID {
|
public static class SSID {
|
||||||
public class Association {
|
public static class Association {
|
||||||
public class Rate {
|
public static class Rate {
|
||||||
public long bitrate;
|
public long bitrate;
|
||||||
public int chwidth;
|
public int chwidth;
|
||||||
public boolean sgi;
|
public boolean sgi;
|
||||||
@@ -66,7 +66,7 @@ public class State {
|
|||||||
public JsonObject radio;
|
public JsonObject radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Counters {
|
public static class Counters {
|
||||||
public long collisions;
|
public long collisions;
|
||||||
public long multicast;
|
public long multicast;
|
||||||
public long rx_bytes;
|
public long rx_bytes;
|
||||||
@@ -96,8 +96,8 @@ public class State {
|
|||||||
|
|
||||||
public Interface[] interfaces;
|
public Interface[] interfaces;
|
||||||
|
|
||||||
public class Unit {
|
public static class Unit {
|
||||||
public class Memory {
|
public static class Memory {
|
||||||
public long buffered;
|
public long buffered;
|
||||||
public long cached;
|
public long cached;
|
||||||
public long free;
|
public long free;
|
||||||
@@ -112,8 +112,21 @@ public class State {
|
|||||||
|
|
||||||
public Unit unit;
|
public Unit unit;
|
||||||
|
|
||||||
|
public static class Radio {
|
||||||
|
public long active_ms;
|
||||||
|
public long busy_ms;
|
||||||
|
public int channel;
|
||||||
|
public String channel_width;
|
||||||
|
public long noise;
|
||||||
|
public String phy;
|
||||||
|
public long receive_ms;
|
||||||
|
public long transmit_ms;
|
||||||
|
public int tx_power;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Radio[] radios;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
public JsonObject[] radios;
|
|
||||||
@SerializedName("link-state") public JsonObject linkState;
|
@SerializedName("link-state") public JsonObject linkState;
|
||||||
public JsonObject gps;
|
public JsonObject gps;
|
||||||
public JsonObject poe;
|
public JsonObject poe;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.models.ap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class UCentralSchema {
|
||||||
|
public static class Radio {
|
||||||
|
public String band;
|
||||||
|
public int bandwidth;
|
||||||
|
// either "auto" or int
|
||||||
|
public JsonPrimitive channel;
|
||||||
|
@SerializedName("valid-channels") public int[] validChannels;
|
||||||
|
public String country;
|
||||||
|
@SerializedName("allow-dfs") public boolean allowDfs = true;
|
||||||
|
@SerializedName("channel-mode") public String channelMode = "HE";
|
||||||
|
@SerializedName("channel-wdith") public int channelWidth = 80;
|
||||||
|
@SerializedName("require-mode") public String requireMode;
|
||||||
|
public String mimo;
|
||||||
|
@SerializedName("tx-power") public int txPower;
|
||||||
|
@SerializedName("legacy-rates") public boolean legacyRates = false;
|
||||||
|
@SerializedName("beacon-interval") public int beaconInterval = 100;
|
||||||
|
@SerializedName("dtim-period") public int dtimPeriod = 2;
|
||||||
|
@SerializedName("maximum-clients") public int maximumClients;
|
||||||
|
|
||||||
|
public static class Rates {
|
||||||
|
public int beacon = 6000;
|
||||||
|
public int multicast = 24000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rates rates;
|
||||||
|
|
||||||
|
public static class HESettings {
|
||||||
|
@SerializedName(
|
||||||
|
"multiple-bssid"
|
||||||
|
) public boolean multipleBssid = false;
|
||||||
|
public boolean ema = false;
|
||||||
|
@SerializedName("bss-color") public int bssColor = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("he-settings") public HESettings heSettings;
|
||||||
|
|
||||||
|
@SerializedName("hostapd-iface-raw") public String[] hostapdIfaceRaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Radio> radios;
|
||||||
|
|
||||||
|
public static class Metrics {
|
||||||
|
public static class Statistics {
|
||||||
|
public int interval;
|
||||||
|
public List<String> types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Statistics statistics;
|
||||||
|
|
||||||
|
public static class Health {
|
||||||
|
public int interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Health health;
|
||||||
|
|
||||||
|
public static class WifiFrames {
|
||||||
|
public List<String> filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("wifi-frames") public WifiFrames wifiFrames;
|
||||||
|
|
||||||
|
public static class DhcpSnooping {
|
||||||
|
public List<String> filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("dhcp-snooping") public DhcpSnooping dhcpSnooping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Metrics metrics;
|
||||||
|
|
||||||
|
// metrics
|
||||||
|
// TODO the below fields are unused right now - include them as necessary
|
||||||
|
// unit
|
||||||
|
// globals
|
||||||
|
// definitions
|
||||||
|
// ethernet
|
||||||
|
// switch
|
||||||
|
// interfaces
|
||||||
|
// services
|
||||||
|
}
|
||||||
@@ -6,13 +6,15 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.models;
|
package com.facebook.openwifi.cloudsdk.models.ap;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
/**
|
||||||
|
* Represents a single entry in wifi scan results.
|
||||||
/** Represents a single entry in wifi scan results. */
|
* ies[] array is not stored directly, but parsed into WifiScanEntry fields
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class WifiScanEntryResult {
|
public class WifiScanEntryResult {
|
||||||
public int channel;
|
public int channel;
|
||||||
public long last_seen;
|
public long last_seen;
|
||||||
@@ -50,8 +52,6 @@ public class WifiScanEntryResult {
|
|||||||
public String vht_oper;
|
public String vht_oper;
|
||||||
public int capability;
|
public int capability;
|
||||||
public int frequency;
|
public int frequency;
|
||||||
/** IE = information element */
|
|
||||||
public JsonArray ies;
|
|
||||||
|
|
||||||
/** Default Constructor. */
|
/** Default Constructor. */
|
||||||
public WifiScanEntryResult() {}
|
public WifiScanEntryResult() {}
|
||||||
@@ -68,7 +68,6 @@ public class WifiScanEntryResult {
|
|||||||
this.vht_oper = o.vht_oper;
|
this.vht_oper = o.vht_oper;
|
||||||
this.capability = o.capability;
|
this.capability = o.capability;
|
||||||
this.frequency = o.frequency;
|
this.frequency = o.frequency;
|
||||||
this.ies = o.ies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,7 +78,6 @@ public class WifiScanEntryResult {
|
|||||||
channel,
|
channel,
|
||||||
frequency,
|
frequency,
|
||||||
ht_oper,
|
ht_oper,
|
||||||
ies,
|
|
||||||
last_seen,
|
last_seen,
|
||||||
signal,
|
signal,
|
||||||
ssid,
|
ssid,
|
||||||
@@ -104,7 +102,9 @@ public class WifiScanEntryResult {
|
|||||||
capability == other.capability && channel == other.channel &&
|
capability == other.capability && channel == other.channel &&
|
||||||
frequency == other.frequency && Objects
|
frequency == other.frequency && Objects
|
||||||
.equals(ht_oper, other.ht_oper) &&
|
.equals(ht_oper, other.ht_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);
|
last_seen == other.last_seen &&
|
||||||
|
Objects.equals(ssid, other.ssid) && tsf == other.tsf &&
|
||||||
|
Objects.equals(vht_oper, other.vht_oper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class AclTemplate {
|
public class AclTemplate {
|
||||||
public boolean Read;
|
public boolean Read;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||||
|
|
||||||
public class DeviceCapabilities {
|
public class DeviceCapabilities {
|
||||||
public JsonObject capabilities;
|
public Capabilities capabilities;
|
||||||
public long firstUpdate;
|
public long firstUpdate;
|
||||||
public long lastUpdate;
|
public long lastUpdate;
|
||||||
public String serialNumber;
|
public String serialNumber;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class DeviceConfigureRequest {
|
public class DeviceConfigureRequest {
|
||||||
public String serialNumber;
|
public String serialNumber;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,6 +6,6 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public enum DeviceType { AP, SWITCH, IOT, MESH }
|
public enum DeviceType { AP, SWITCH, IOT, MESH }
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class MfaAuthInfo {
|
public class MfaAuthInfo {
|
||||||
public boolean enabled;
|
public boolean enabled;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class MobilePhoneNumber {
|
public class MobilePhoneNumber {
|
||||||
public String number;
|
public String number;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class NoteInfo {
|
public class NoteInfo {
|
||||||
public long created;
|
public long created;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class ServiceEvent {
|
public class ServiceEvent {
|
||||||
public static final String EVENT_JOIN = "join";
|
public static final String EVENT_JOIN = "join";
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class TokenValidationResult {
|
public class TokenValidationResult {
|
||||||
public UserInfo userInfo;
|
public UserInfo userInfo;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public enum VerifiedCertificate {
|
public enum VerifiedCertificate {
|
||||||
NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED
|
NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class WebTokenAclTemplate {
|
public class WebTokenAclTemplate {
|
||||||
public AclTemplate aclTemplate;
|
public AclTemplate aclTemplate;
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
public class WebTokenResult {
|
public class WebTokenResult {
|
||||||
public String access_token;
|
public String access_token;
|
||||||
public String refresh_token;
|
public String refresh_token;
|
||||||
public String token_type;
|
public String token_type;
|
||||||
public int expires_in;
|
public long expires_in;
|
||||||
public int idle_timeout;
|
public int idle_timeout;
|
||||||
public String username;
|
public String username;
|
||||||
public long created;
|
public long created;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
package com.facebook.openwifi.cloudsdk.models.gw;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,14 +6,14 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
|
||||||
|
|
||||||
public class DeviceConfiguration {
|
public class DeviceConfiguration {
|
||||||
public class DeviceConfigurationElement {
|
public static class DeviceConfigurationElement {
|
||||||
public String name;
|
public String name;
|
||||||
public String description;
|
public String description;
|
||||||
public Integer weight;
|
public Integer weight;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
public class DeviceRules {
|
public class DeviceRules {
|
||||||
public String rcOnly;
|
public String rcOnly;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
public class DiGraphEntry {
|
public class DiGraphEntry {
|
||||||
public String parent;
|
public String parent;
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
|
||||||
|
|
||||||
public class Entity {
|
public class Entity {
|
||||||
// from ObjectInfo
|
// from ObjectInfo
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
|
||||||
|
|
||||||
public class InventoryTag {
|
public class InventoryTag {
|
||||||
// from ObjectInfo
|
// from ObjectInfo
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
public class RRMAlgorithmDetails {
|
public class RRMAlgorithmDetails {
|
||||||
public String name;
|
public String name;
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class RRMDetails {
|
public class RRMDetails {
|
||||||
public class RRMDetailsImpl {
|
public static class RRMDetailsImpl {
|
||||||
public String vendor;
|
public String vendor;
|
||||||
public String schedule;
|
public String schedule;
|
||||||
public List<RRMAlgorithmDetails> algorithms;
|
public List<RRMAlgorithmDetails> algorithms;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,11 +6,11 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
|
||||||
|
|
||||||
public class Venue {
|
public class Venue {
|
||||||
// from ObjectInfo
|
// from ObjectInfo
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.models;
|
package com.facebook.openwifi.cloudsdk.models.prov;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.rrm.models;
|
package com.facebook.openwifi.cloudsdk.models.prov.rrm;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.prov.rrm.models;
|
package com.facebook.openwifi.cloudsdk.models.prov.rrm;
|
||||||
|
|
||||||
public class Provider {
|
public class Provider {
|
||||||
public String vendor;
|
public String vendor;
|
||||||
6
lib-cloudsdk/src/main/resources/log4j.properties
Normal file
6
lib-cloudsdk/src/main/resources/log4j.properties
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class UCentralUtilsTest {
|
||||||
|
@Test
|
||||||
|
void test_placeholder() throws Exception {
|
||||||
|
assertEquals(3, 1 + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test_setRadioConfigFieldChannel() throws Exception {
|
||||||
|
final String serialNumber = "aaaaaaaaaaaa";
|
||||||
|
final int expectedChannel = 1;
|
||||||
|
final Map<String, Integer> newValueList = Collections
|
||||||
|
.singletonMap(UCentralConstants.BAND_5G, expectedChannel);
|
||||||
|
|
||||||
|
// test case where channel value is a string and not an integer
|
||||||
|
UCentralApConfiguration config = new UCentralApConfiguration(
|
||||||
|
"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": \"auto\"}]}"
|
||||||
|
);
|
||||||
|
boolean modified = UCentralUtils
|
||||||
|
.setRadioConfigChannel(serialNumber, config, newValueList);
|
||||||
|
assertTrue(modified);
|
||||||
|
assertEquals(
|
||||||
|
config.getRadioConfig(0).channel.getAsInt(),
|
||||||
|
expectedChannel
|
||||||
|
);
|
||||||
|
|
||||||
|
// field doesn't exist
|
||||||
|
config = new UCentralApConfiguration(
|
||||||
|
"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\"}]}"
|
||||||
|
);
|
||||||
|
modified = UCentralUtils
|
||||||
|
.setRadioConfigChannel(serialNumber, config, newValueList);
|
||||||
|
assertTrue(modified);
|
||||||
|
assertEquals(
|
||||||
|
config.getRadioConfig(0).channel.getAsInt(),
|
||||||
|
expectedChannel
|
||||||
|
);
|
||||||
|
|
||||||
|
// normal field, not modified
|
||||||
|
config = new UCentralApConfiguration(
|
||||||
|
"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": 1}]}"
|
||||||
|
);
|
||||||
|
modified = UCentralUtils
|
||||||
|
.setRadioConfigChannel(serialNumber, config, newValueList);
|
||||||
|
assertFalse(modified);
|
||||||
|
assertEquals(
|
||||||
|
config.getRadioConfig(0).channel.getAsInt(),
|
||||||
|
expectedChannel
|
||||||
|
);
|
||||||
|
|
||||||
|
// normal field, modified
|
||||||
|
config = new UCentralApConfiguration(
|
||||||
|
"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": 15}]}"
|
||||||
|
);
|
||||||
|
modified = UCentralUtils
|
||||||
|
.setRadioConfigChannel(serialNumber, config, newValueList);
|
||||||
|
assertTrue(modified);
|
||||||
|
assertEquals(
|
||||||
|
config.getRadioConfig(0).channel.getAsInt(),
|
||||||
|
expectedChannel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,17 +6,17 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
package com.facebook.openwifi.cloudsdk.ies;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class HTOperationElementTest {
|
public class HTOperationTest {
|
||||||
@Test
|
@Test
|
||||||
void testGetHtOper() {
|
void testGetHtOper() {
|
||||||
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
HTOperation htOperObj = new HTOperation(htOper);
|
||||||
byte expectedPrimaryChannel = 1;
|
byte expectedPrimaryChannel = 1;
|
||||||
byte expectedSecondaryChannelOffset = 0;
|
byte expectedSecondaryChannelOffset = 0;
|
||||||
boolean expectedStaChannelWidth = false;
|
boolean expectedStaChannelWidth = false;
|
||||||
@@ -28,7 +28,7 @@ public class HTOperationElementTest {
|
|||||||
boolean expectedDualBeacon = false;
|
boolean expectedDualBeacon = false;
|
||||||
boolean expectedDualCtsProtection = false;
|
boolean expectedDualCtsProtection = false;
|
||||||
boolean expectedStbcBeacon = false;
|
boolean expectedStbcBeacon = false;
|
||||||
HTOperationElement expectedHtOperObj = new HTOperationElement(
|
HTOperation expectedHtOperObj = new HTOperation(
|
||||||
expectedPrimaryChannel,
|
expectedPrimaryChannel,
|
||||||
expectedSecondaryChannelOffset,
|
expectedSecondaryChannelOffset,
|
||||||
expectedStaChannelWidth,
|
expectedStaChannelWidth,
|
||||||
@@ -44,11 +44,11 @@ public class HTOperationElementTest {
|
|||||||
assertEquals(expectedHtOperObj, htOperObj);
|
assertEquals(expectedHtOperObj, htOperObj);
|
||||||
|
|
||||||
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||||
htOperObj = new HTOperationElement(htOper);
|
htOperObj = new HTOperation(htOper);
|
||||||
// all fields except the primary channel and nongreenfield field are the same
|
// all fields except the primary channel and nongreenfield field are the same
|
||||||
expectedPrimaryChannel = 36;
|
expectedPrimaryChannel = 36;
|
||||||
expectedNongreenfieldHtStasPresent = false;
|
expectedNongreenfieldHtStasPresent = false;
|
||||||
expectedHtOperObj = new HTOperationElement(
|
expectedHtOperObj = new HTOperation(
|
||||||
expectedPrimaryChannel,
|
expectedPrimaryChannel,
|
||||||
expectedSecondaryChannelOffset,
|
expectedSecondaryChannelOffset,
|
||||||
expectedStaChannelWidth,
|
expectedStaChannelWidth,
|
||||||
@@ -6,23 +6,23 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
package com.facebook.openwifi.cloudsdk.ies;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class VHTOperationElementTest {
|
public class VHTOperationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetVhtOper() {
|
void testGetVhtOper() {
|
||||||
String vhtOper = "ACQAAAA=";
|
String vhtOper = "ACQAAAA=";
|
||||||
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
|
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
|
||||||
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
|
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
|
||||||
byte expectedChannel1 = 36;
|
short expectedChannel1 = 36;
|
||||||
byte expectedChannel2 = 0;
|
short expectedChannel2 = 0;
|
||||||
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
VHTOperationElement expectedVhtOperObj = new VHTOperationElement(
|
VHTOperation expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
@@ -31,12 +31,12 @@ public class VHTOperationElementTest {
|
|||||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||||
|
|
||||||
vhtOper = "AToAUAE=";
|
vhtOper = "AToAUAE=";
|
||||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
expectedChannelWidthIndicator = 1; // 80 MHz channel width
|
expectedChannelWidthIndicator = 1; // 80 MHz channel width
|
||||||
expectedChannel1 = 58;
|
expectedChannel1 = 58;
|
||||||
// same channel2
|
// same channel2
|
||||||
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
|
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
|
||||||
expectedVhtOperObj = new VHTOperationElement(
|
expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
@@ -45,12 +45,27 @@ public class VHTOperationElementTest {
|
|||||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||||
|
|
||||||
vhtOper = "ASoyUAE=";
|
vhtOper = "ASoyUAE=";
|
||||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
// same channel width indicator (160 MHz channel width)
|
// same channel width indicator (160 MHz channel width)
|
||||||
expectedChannel1 = 42;
|
expectedChannel1 = 42;
|
||||||
expectedChannel2 = 50;
|
expectedChannel2 = 50;
|
||||||
// same vhtMcsForNss
|
// same vhtMcsForNss
|
||||||
expectedVhtOperObj = new VHTOperationElement(
|
expectedVhtOperObj = new VHTOperation(
|
||||||
|
expectedChannelWidthIndicator,
|
||||||
|
expectedChannel1,
|
||||||
|
expectedChannel2,
|
||||||
|
expectedVhtMcsForNss
|
||||||
|
);
|
||||||
|
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||||
|
|
||||||
|
// test with channel number >= 128 (channel fields should be unsigned)
|
||||||
|
vhtOper = "AJUAAAA=";
|
||||||
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
|
expectedChannelWidthIndicator = 0;
|
||||||
|
expectedChannel1 = 149;
|
||||||
|
expectedChannel2 = 0;
|
||||||
|
expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
4
owrrm/.gitignore
vendored
Normal file
4
owrrm/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/*.log*
|
||||||
|
/device_config.json
|
||||||
|
/settings.json
|
||||||
|
/topology.json
|
||||||
39
owrrm/README.md
Normal file
39
owrrm/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 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/
|
||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -478,8 +478,10 @@ components:
|
|||||||
RRMSchedule:
|
RRMSchedule:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
cron:
|
crons:
|
||||||
type: string
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
algorithms:
|
algorithms:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
139
owrrm/pom.xml
Normal file
139
owrrm/pom.xml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<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>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
@@ -18,11 +18,10 @@ import org.json.JSONObject;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
|
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
@@ -155,8 +154,7 @@ public class Launcher implements Callable<Integer> {
|
|||||||
: DEFAULT_DEVICE_LAYERED_CONFIG_FILE
|
: DEFAULT_DEVICE_LAYERED_CONFIG_FILE
|
||||||
);
|
);
|
||||||
|
|
||||||
String serviceKey =
|
String serviceKey = Utils.generateServiceKey(config.serviceConfig);
|
||||||
UCentralUtils.generateServiceKey(config.serviceConfig);
|
|
||||||
|
|
||||||
// Instantiate clients
|
// Instantiate clients
|
||||||
UCentralClient.verifySsl(config.uCentralConfig.verifySsl);
|
UCentralClient.verifySsl(config.uCentralConfig.verifySsl);
|
||||||
@@ -166,7 +164,9 @@ public class Launcher implements Callable<Integer> {
|
|||||||
config.uCentralConfig.uCentralSecPublicEndpoint,
|
config.uCentralConfig.uCentralSecPublicEndpoint,
|
||||||
config.uCentralConfig.username,
|
config.uCentralConfig.username,
|
||||||
config.uCentralConfig.password,
|
config.uCentralConfig.password,
|
||||||
config.uCentralConfig.uCentralSocketParams
|
config.uCentralConfig.uCentralSocketParams.connectTimeoutMs,
|
||||||
|
config.uCentralConfig.uCentralSocketParams.socketTimeoutMs,
|
||||||
|
config.uCentralConfig.uCentralSocketParams.wifiScanTimeoutMs
|
||||||
);
|
);
|
||||||
UCentralKafkaConsumer consumer;
|
UCentralKafkaConsumer consumer;
|
||||||
UCentralKafkaProducer producer;
|
UCentralKafkaProducer producer;
|
||||||
@@ -265,7 +265,7 @@ public class Launcher implements Callable<Integer> {
|
|||||||
.setPrettyPrinting()
|
.setPrettyPrinting()
|
||||||
.serializeNulls() // for here only!!
|
.serializeNulls() // for here only!!
|
||||||
.create();
|
.create();
|
||||||
logger.info(gson.toJson(DeviceConfig.createDefault()));
|
System.out.println(gson.toJson(DeviceConfig.createDefault()));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -18,18 +18,18 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.modules.ApiServer;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
import com.facebook.openwifi.cloudsdk.kafka.KafkaRunner;
|
||||||
import com.facebook.openwifirrm.modules.DataCollector;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||||
import com.facebook.openwifirrm.modules.Modeler;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
|
||||||
import com.facebook.openwifirrm.modules.ProvMonitor;
|
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||||
import com.facebook.openwifirrm.modules.RRMScheduler;
|
import com.facebook.openwifi.rrm.modules.ApiServer;
|
||||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||||
import com.facebook.openwifirrm.ucentral.KafkaRunner;
|
import com.facebook.openwifi.rrm.modules.DataCollector;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.rrm.modules.Modeler;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
import com.facebook.openwifi.rrm.modules.ProvMonitor;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
|
import com.facebook.openwifi.rrm.modules.RRMScheduler;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RRM service runner.
|
* RRM service runner.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -14,17 +14,17 @@ import java.util.Map;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||||
import com.facebook.openwifirrm.modules.Modeler;
|
import com.facebook.openwifi.rrm.modules.Modeler;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
|
import com.facebook.openwifi.rrm.optimizers.channel.ChannelOptimizer;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
|
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
|
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
|
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
|
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
|
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.TPC;
|
import com.facebook.openwifi.rrm.optimizers.tpc.TPC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RRM algorithm model and utility methods.
|
* RRM algorithm model and utility methods.
|
||||||
@@ -143,6 +143,9 @@ public class RRMAlgorithm {
|
|||||||
* @param dryRun if set, do not apply changes
|
* @param dryRun if set, do not apply changes
|
||||||
* @param allowDefaultMode if false, "mode" argument must be present and
|
* @param allowDefaultMode if false, "mode" argument must be present and
|
||||||
* valid (returns error if invalid)
|
* valid (returns error if invalid)
|
||||||
|
* @param updateImmediately true if the method should queue the zone for
|
||||||
|
* update and interrupt the config manager thread
|
||||||
|
* to trigger immediate update
|
||||||
*
|
*
|
||||||
* @return the algorithm result, with exactly one field set ("error" upon
|
* @return the algorithm result, with exactly one field set ("error" upon
|
||||||
* failure, any others upon success)
|
* failure, any others upon success)
|
||||||
@@ -153,7 +156,8 @@ public class RRMAlgorithm {
|
|||||||
Modeler modeler,
|
Modeler modeler,
|
||||||
String zone,
|
String zone,
|
||||||
boolean dryRun,
|
boolean dryRun,
|
||||||
boolean allowDefaultMode
|
boolean allowDefaultMode,
|
||||||
|
boolean updateImmediately
|
||||||
) {
|
) {
|
||||||
AlgorithmResult result = new AlgorithmResult();
|
AlgorithmResult result = new AlgorithmResult();
|
||||||
if (name == null || args == null) {
|
if (name == null || args == null) {
|
||||||
@@ -212,11 +216,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.channelMap = optimizer.computeChannelMap();
|
result.channelMap = optimizer.computeChannelMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.channelMap
|
result.channelMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
||||||
@@ -270,11 +277,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.txPowerMap = optimizer.computeTxPowerMap();
|
result.txPowerMap = optimizer.computeTxPowerMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.txPowerMap
|
result.txPowerMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.error = String.format("Unknown algorithm: '%s'", name);
|
result.error = String.format("Unknown algorithm: '%s'", name);
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public class RRMConfig {
|
|||||||
* Private endpoint for the RRM service
|
* Private endpoint for the RRM service
|
||||||
* ({@code SERVICECONFIG_PRIVATEENDPOINT})
|
* ({@code SERVICECONFIG_PRIVATEENDPOINT})
|
||||||
*/
|
*/
|
||||||
public String privateEndpoint = "http://owrrm.wlan.local:16789"; // see ApiServerParams.httpPort
|
public String privateEndpoint = "http://owrrm.wlan.local:16790"; // see ApiServerParams.internalHttpPort
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public endpoint for the RRM service
|
* Public endpoint for the RRM service
|
||||||
@@ -60,7 +60,7 @@ public class RRMConfig {
|
|||||||
* ({@code SERVICECONFIG_VENDORREFERENCEURL})
|
* ({@code SERVICECONFIG_VENDORREFERENCEURL})
|
||||||
*/
|
*/
|
||||||
public String vendorReferenceUrl =
|
public String vendorReferenceUrl =
|
||||||
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/ALGORITHMS.md";
|
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/owrrm/ALGORITHMS.md";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Service configuration. */
|
/** Service configuration. */
|
||||||
@@ -232,7 +232,7 @@ public class RRMConfig {
|
|||||||
* The main logic loop interval (i.e. sleep time), in ms
|
* The main logic loop interval (i.e. sleep time), in ms
|
||||||
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
|
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int updateIntervalMs = 5000;
|
public int updateIntervalMs = 30000; // 30sec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expected device statistics interval, in seconds (or -1 to
|
* The expected device statistics interval, in seconds (or -1 to
|
||||||
@@ -246,13 +246,13 @@ public class RRMConfig {
|
|||||||
* automatic scans)
|
* automatic scans)
|
||||||
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
|
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
|
||||||
*/
|
*/
|
||||||
public int wifiScanIntervalSec = 900;
|
public int wifiScanIntervalSec = 900; // 15min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The capabilities request interval (per device), in seconds
|
* The capabilities request interval (per device), in seconds
|
||||||
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
|
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
|
||||||
*/
|
*/
|
||||||
public int capabilitiesIntervalSec = 3600;
|
public int capabilitiesIntervalSec = 3600; // 1hr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of executor threads for async tasks (ex. wifi scans)
|
* 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
|
* The main logic loop interval (i.e. sleep time), in ms
|
||||||
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
|
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int updateIntervalMs = 60000;
|
public int updateIntervalMs = 120000; // 2min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable pushing device config changes?
|
* Enable pushing device config changes?
|
||||||
@@ -309,6 +309,12 @@ public class RRMConfig {
|
|||||||
* ({@code MODELERPARAMS_WIFISCANBUFFERSIZE})
|
* ({@code MODELERPARAMS_WIFISCANBUFFERSIZE})
|
||||||
*/
|
*/
|
||||||
public int wifiScanBufferSize = 10;
|
public int wifiScanBufferSize = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum rounds of States to store per device
|
||||||
|
* ({@code MODELERPARAMS_STATEBUFFERSIZE})
|
||||||
|
*/
|
||||||
|
public int stateBufferSize = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Modeler parameters. */
|
/** Modeler parameters. */
|
||||||
@@ -319,10 +325,16 @@ public class RRMConfig {
|
|||||||
*/
|
*/
|
||||||
public class ApiServerParams {
|
public class ApiServerParams {
|
||||||
/**
|
/**
|
||||||
* The HTTP port to listen on, or -1 to disable
|
* The HTTP port to listen on for internal traffic, or -1 to disable
|
||||||
* ({@code APISERVERPARAMS_HTTPPORT})
|
* ({@code APISERVERPARAMS_INTERNALHTTPPORT})
|
||||||
*/
|
*/
|
||||||
public int httpPort = 16789;
|
public int internalHttpPort = 16790;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP port to listen on for external traffic, or -1 to disable
|
||||||
|
* ({@code APISERVERPARAMS_EXTERNALHTTPPORT})
|
||||||
|
*/
|
||||||
|
public int externalHttpPort = 16789;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comma-separated list of all allowed CORS domains (exact match
|
* Comma-separated list of all allowed CORS domains (exact match
|
||||||
@@ -363,7 +375,7 @@ public class RRMConfig {
|
|||||||
* Sync interval, in ms, for owprov venue information etc.
|
* Sync interval, in ms, for owprov venue information etc.
|
||||||
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
|
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int syncIntervalMs = 300000;
|
public int syncIntervalMs = 300000; // 5min
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ProvMonitor parameters. */
|
/** ProvMonitor parameters. */
|
||||||
@@ -532,10 +544,16 @@ public class RRMConfig {
|
|||||||
if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) {
|
if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) {
|
||||||
modelerParams.wifiScanBufferSize = Integer.parseInt(v);
|
modelerParams.wifiScanBufferSize = Integer.parseInt(v);
|
||||||
}
|
}
|
||||||
|
if ((v = env.get("MODELERPARAMS_STATEBUFFERSIZE")) != null) {
|
||||||
|
modelerParams.stateBufferSize = Integer.parseInt(v);
|
||||||
|
}
|
||||||
ModuleConfig.ApiServerParams apiServerParams =
|
ModuleConfig.ApiServerParams apiServerParams =
|
||||||
config.moduleConfig.apiServerParams;
|
config.moduleConfig.apiServerParams;
|
||||||
if ((v = env.get("APISERVERPARAMS_HTTPPORT")) != null) {
|
if ((v = env.get("APISERVERPARAMS_INTERNALHTTPPORT")) != null) {
|
||||||
apiServerParams.httpPort = Integer.parseInt(v);
|
apiServerParams.internalHttpPort = Integer.parseInt(v);
|
||||||
|
}
|
||||||
|
if ((v = env.get("APISERVERPARAMS_EXTERNALHTTPPORT")) != null) {
|
||||||
|
apiServerParams.externalHttpPort = Integer.parseInt(v);
|
||||||
}
|
}
|
||||||
if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) {
|
if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) {
|
||||||
apiServerParams.corsDomainList = v;
|
apiServerParams.corsDomainList = v;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -19,9 +19,9 @@ public class RRMSchedule {
|
|||||||
*
|
*
|
||||||
* This field expects a cron-like format as defined by the Quartz Job
|
* This field expects a cron-like format as defined by the Quartz Job
|
||||||
* Scheduler (CronTrigger):
|
* Scheduler (CronTrigger):
|
||||||
* https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
|
* https://www.quartz-scheduler.org/documentation/quartz-2.4.0/tutorials/crontrigger.html
|
||||||
*/
|
*/
|
||||||
public String cron;
|
public List<String> crons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of RRM algorithms to run.
|
* The list of RRM algorithms to run.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -16,6 +16,8 @@ import java.io.InputStream;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
@@ -23,6 +25,8 @@ import java.util.concurrent.ThreadFactory;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -31,6 +35,8 @@ import com.google.gson.GsonBuilder;
|
|||||||
* Generic utility methods.
|
* Generic utility methods.
|
||||||
*/
|
*/
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(Utils.class);
|
||||||
|
|
||||||
/** Hex value array for use in {@link #longToMac(long)}. */
|
/** Hex value array for use in {@link #longToMac(long)}. */
|
||||||
private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray();
|
private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray();
|
||||||
|
|
||||||
@@ -193,4 +199,19 @@ public class Utils {
|
|||||||
public static <T> T deepCopy(T obj, Class<T> classOfT) {
|
public static <T> T deepCopy(T obj, Class<T> classOfT) {
|
||||||
return gson.fromJson(gson.toJson(obj), 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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm;
|
package com.facebook.openwifi.rrm;
|
||||||
|
|
||||||
import picocli.CommandLine.IVersionProvider;
|
import picocli.CommandLine.IVersionProvider;
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.aggregators;
|
package com.facebook.openwifi.rrm.aggregators;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aggregates added values into one "aggregate" measure.
|
* Aggregates added values into one "aggregate" measure.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.aggregators;
|
package com.facebook.openwifi.rrm.aggregators;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the mean of all added values. If no values are added, the mean is 0.
|
* Tracks the mean of all added values. If no values are added, the mean is 0.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -35,28 +35,29 @@ import org.reflections.util.ConfigurationBuilder;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||||
import com.facebook.openwifirrm.DeviceLayeredConfig;
|
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
|
||||||
import com.facebook.openwifirrm.DeviceTopology;
|
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Algorithm;
|
||||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Provider;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ApiServerParams;
|
import com.facebook.openwifi.rrm.CustomJettyServerFactory;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ServiceConfig;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.Utils.LruCache;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.VersionProvider;
|
import com.facebook.openwifi.rrm.DeviceLayeredConfig;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
|
import com.facebook.openwifi.rrm.DeviceTopology;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
|
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
import com.facebook.openwifi.rrm.Utils;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
|
import com.facebook.openwifi.rrm.VersionProvider;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
|
import com.facebook.openwifi.rrm.RRMConfig.ServiceConfig;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ApiServerParams;
|
||||||
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
|
import com.facebook.openwifi.rrm.Utils.LruCache;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
|
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Algorithm;
|
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Provider;
|
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||||
|
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
@@ -81,7 +82,9 @@ import io.swagger.v3.oas.models.OpenAPI;
|
|||||||
import spark.Request;
|
import spark.Request;
|
||||||
import spark.Response;
|
import spark.Response;
|
||||||
import spark.Route;
|
import spark.Route;
|
||||||
import spark.Spark;
|
import spark.Service;
|
||||||
|
import spark.embeddedserver.EmbeddedServers;
|
||||||
|
import spark.embeddedserver.jetty.EmbeddedJettyFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP API server.
|
* HTTP API server.
|
||||||
@@ -110,6 +113,27 @@ public class ApiServer implements Runnable {
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(ApiServer.class);
|
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. */
|
/** The module parameters. */
|
||||||
private final ApiServerParams params;
|
private final ApiServerParams params;
|
||||||
|
|
||||||
@@ -164,9 +188,10 @@ public class ApiServer implements Runnable {
|
|||||||
UCentralClient client,
|
UCentralClient client,
|
||||||
RRMScheduler scheduler
|
RRMScheduler scheduler
|
||||||
) {
|
) {
|
||||||
|
this.service = Service.ignite();
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.serviceConfig = serviceConfig;
|
this.serviceConfig = serviceConfig;
|
||||||
this.serviceKey = UCentralUtils.generateServiceKey(serviceConfig);
|
this.serviceKey = Utils.generateServiceKey(serviceConfig);
|
||||||
this.deviceDataManager = deviceDataManager;
|
this.deviceDataManager = deviceDataManager;
|
||||||
this.configManager = configManager;
|
this.configManager = configManager;
|
||||||
this.modeler = modeler;
|
this.modeler = modeler;
|
||||||
@@ -194,64 +219,116 @@ public class ApiServer implements Runnable {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until initialization finishes. Just calls the method on the
|
||||||
|
* underlying service.
|
||||||
|
*/
|
||||||
|
public void awaitInitialization() {
|
||||||
|
service.awaitInitialization();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
this.startTimeMs = System.currentTimeMillis();
|
this.startTimeMs = System.currentTimeMillis();
|
||||||
|
|
||||||
if (params.httpPort == -1) {
|
if (params.internalHttpPort == -1 && params.externalHttpPort == -1) {
|
||||||
logger.info("API server is disabled.");
|
logger.info("API server is disabled.");
|
||||||
return;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spark.port(params.httpPort);
|
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);
|
||||||
|
|
||||||
// Configure API docs hosting
|
// Configure API docs hosting
|
||||||
Spark.staticFiles.location("/public");
|
service.staticFiles.location("/public");
|
||||||
Spark.get("/openapi.yaml", this::getOpenApiYaml);
|
service.get("/openapi.yaml", this::getOpenApiYaml);
|
||||||
Spark.get("/openapi.json", this::getOpenApiJson);
|
service.get("/openapi.json", this::getOpenApiJson);
|
||||||
|
|
||||||
// Install routes
|
// Install routes
|
||||||
Spark.before(this::beforeFilter);
|
service.before(this::beforeFilter);
|
||||||
Spark.after(this::afterFilter);
|
service.after(this::afterFilter);
|
||||||
Spark.options("/*", this::options);
|
service.options("/*", this::options);
|
||||||
Spark.get("/api/v1/system", new SystemEndpoint());
|
service.get("/api/v1/system", new SystemEndpoint());
|
||||||
Spark.post("/api/v1/system", new SetSystemEndpoint());
|
service.post("/api/v1/system", new SetSystemEndpoint());
|
||||||
Spark.get("/api/v1/provider", new ProviderEndpoint());
|
service.get("/api/v1/provider", new ProviderEndpoint());
|
||||||
Spark.get("/api/v1/algorithms", new AlgorithmsEndpoint());
|
service.get("/api/v1/algorithms", new AlgorithmsEndpoint());
|
||||||
Spark.put("/api/v1/runRRM", new RunRRMEndpoint());
|
service.put("/api/v1/runRRM", new RunRRMEndpoint());
|
||||||
Spark.get("/api/v1/getTopology", new GetTopologyEndpoint());
|
service.get("/api/v1/getTopology", new GetTopologyEndpoint());
|
||||||
Spark.post("/api/v1/setTopology", new SetTopologyEndpoint());
|
service.post("/api/v1/setTopology", new SetTopologyEndpoint());
|
||||||
Spark.get(
|
service.get(
|
||||||
"/api/v1/getDeviceLayeredConfig",
|
"/api/v1/getDeviceLayeredConfig",
|
||||||
new GetDeviceLayeredConfigEndpoint()
|
new GetDeviceLayeredConfigEndpoint()
|
||||||
);
|
);
|
||||||
Spark.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
|
service.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
|
||||||
Spark.post(
|
service.post(
|
||||||
"/api/v1/setDeviceNetworkConfig",
|
"/api/v1/setDeviceNetworkConfig",
|
||||||
new SetDeviceNetworkConfigEndpoint()
|
new SetDeviceNetworkConfigEndpoint()
|
||||||
);
|
);
|
||||||
Spark.post(
|
service.post(
|
||||||
"/api/v1/setDeviceZoneConfig",
|
"/api/v1/setDeviceZoneConfig",
|
||||||
new SetDeviceZoneConfigEndpoint()
|
new SetDeviceZoneConfigEndpoint()
|
||||||
);
|
);
|
||||||
Spark.post(
|
service.post(
|
||||||
"/api/v1/setDeviceApConfig",
|
"/api/v1/setDeviceApConfig",
|
||||||
new SetDeviceApConfigEndpoint()
|
new SetDeviceApConfigEndpoint()
|
||||||
);
|
);
|
||||||
Spark.post(
|
service.post(
|
||||||
"/api/v1/modifyDeviceApConfig",
|
"/api/v1/modifyDeviceApConfig",
|
||||||
new ModifyDeviceApConfigEndpoint()
|
new ModifyDeviceApConfigEndpoint()
|
||||||
);
|
);
|
||||||
Spark.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
|
service.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
|
||||||
Spark.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
|
service.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
|
||||||
Spark.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
|
service.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
|
||||||
|
|
||||||
logger.info("API server listening on HTTP port {}", params.httpPort);
|
logger.info(
|
||||||
|
"API server listening for HTTP internal on port {} and external on port {}",
|
||||||
|
params.internalHttpPort,
|
||||||
|
params.externalHttpPort
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop the server. */
|
/** Stop the server. */
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
Spark.stop();
|
service.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until stop finishes. Just calls the method on the underlying service.
|
||||||
|
*/
|
||||||
|
public void awaitStop() {
|
||||||
|
service.awaitStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reconstructs a URL. */
|
/** Reconstructs a URL. */
|
||||||
@@ -269,15 +346,17 @@ public class ApiServer implements Runnable {
|
|||||||
* HTTP 403 response and return false.
|
* HTTP 403 response and return false.
|
||||||
*/
|
*/
|
||||||
private boolean performOpenWifiAuth(Request request, Response response) {
|
private boolean performOpenWifiAuth(Request request, Response response) {
|
||||||
// TODO check if request came from internal endpoint
|
int port = request.port();
|
||||||
boolean internal = true;
|
boolean internal = port > 0 && port == params.internalHttpPort;
|
||||||
String internalName = request.headers("X-INTERNAL-NAME");
|
if (internal) {
|
||||||
if (internal && internalName != null) {
|
String internalName = request.headers("X-INTERNAL-NAME");
|
||||||
// Internal request, validate "X-API-KEY"
|
if (internalName != null) {
|
||||||
String apiKey = request.headers("X-API-KEY");
|
// Internal request, validate "X-API-KEY"
|
||||||
if (apiKey != null && apiKey.equals(serviceKey)) {
|
String apiKey = request.headers("X-API-KEY");
|
||||||
// auth success
|
if (apiKey != null && apiKey.equals(serviceKey)) {
|
||||||
return true;
|
// auth success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// External request, validate token:
|
// External request, validate token:
|
||||||
@@ -297,17 +376,18 @@ public class ApiServer implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// auth failure
|
// auth failure
|
||||||
Spark.halt(403, "Forbidden");
|
service.halt(403, "Forbidden");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate an OpenWiFi token (external), caching successful lookups.
|
* 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).
|
||||||
* @return true if token is valid
|
* @return true if token is valid
|
||||||
*/
|
*/
|
||||||
private boolean validateOpenWifiToken(String token) {
|
private boolean validateOpenWifiToken(String token) {
|
||||||
// The below only checks /api/v1/validateToken and caches it as necessary.
|
// 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);
|
Long expiry = tokenCache.get(token);
|
||||||
if (expiry == null) {
|
if (expiry == null) {
|
||||||
TokenValidationResult result = client.validateToken(token);
|
TokenValidationResult result = client.validateToken(token);
|
||||||
@@ -324,10 +404,11 @@ public class ApiServer implements Runnable {
|
|||||||
private void beforeFilter(Request request, Response response) {
|
private void beforeFilter(Request request, Response response) {
|
||||||
// Log requests
|
// Log requests
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[{}] {} {}",
|
"[{}] {} {} on port {}",
|
||||||
request.ip(),
|
request.ip(),
|
||||||
request.requestMethod(),
|
request.requestMethod(),
|
||||||
getFullUrl(request.pathInfo(), request.queryString())
|
getFullUrl(request.pathInfo(), request.queryString()),
|
||||||
|
request.port()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove "Server: Jetty" header
|
// Remove "Server: Jetty" header
|
||||||
@@ -711,7 +792,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
venue,
|
venue,
|
||||||
mock,
|
mock,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -917,7 +999,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig networkConfig =
|
DeviceConfig networkConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueAllZonesAndWakeUp();
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -981,7 +1063,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig zoneConfig =
|
DeviceConfig zoneConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1044,7 +1126,10 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig apConfig =
|
DeviceConfig apConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
||||||
configManager.wakeUp();
|
// TODO enable updates to device(s), not just the entire zone
|
||||||
|
final String zone =
|
||||||
|
deviceDataManager.getDeviceZone(serialNumber);
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1117,7 +1202,10 @@ public class ApiServer implements Runnable {
|
|||||||
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
||||||
.apply(apConfig);
|
.apply(apConfig);
|
||||||
});
|
});
|
||||||
configManager.wakeUp();
|
final String zone =
|
||||||
|
deviceDataManager.getDeviceZone(serialNumber);
|
||||||
|
// TODO enable updates to device(s), not just the entire zone
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1260,7 +1348,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -1371,7 +1460,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -6,25 +6,28 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ConfigManagerParams;
|
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ConfigManagerParams;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device configuration manager module.
|
* Device configuration manager module.
|
||||||
@@ -63,8 +66,11 @@ public class ConfigManager implements Runnable {
|
|||||||
/** Is the main thread sleeping? */
|
/** Is the main thread sleeping? */
|
||||||
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
||||||
|
|
||||||
/** Was a manual config update requested? */
|
/**
|
||||||
private final AtomicBoolean eventFlag = new AtomicBoolean(false);
|
* Thread-safe set of zones for which manual config updates have been
|
||||||
|
* requested.
|
||||||
|
*/
|
||||||
|
private Set<String> zonesToUpdate = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
/** Config listener interface. */
|
/** Config listener interface. */
|
||||||
public interface ConfigListener {
|
public interface ConfigListener {
|
||||||
@@ -180,7 +186,10 @@ public class ConfigManager implements Runnable {
|
|||||||
List<String> devicesNeedingUpdate = new ArrayList<>();
|
List<String> devicesNeedingUpdate = new ArrayList<>();
|
||||||
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
||||||
params.configDebounceIntervalSec * 1_000_000_000L;
|
params.configDebounceIntervalSec * 1_000_000_000L;
|
||||||
final boolean isEvent = eventFlag.getAndSet(false);
|
Set<String> zonesToUpdateCopy = new HashSet<>(zonesToUpdate);
|
||||||
|
// use removeAll() instead of clear() in case items are added between
|
||||||
|
// the previous line and the following line
|
||||||
|
zonesToUpdate.removeAll(zonesToUpdateCopy);
|
||||||
for (DeviceWithStatus device : devices) {
|
for (DeviceWithStatus device : devices) {
|
||||||
// Update config structure
|
// Update config structure
|
||||||
DeviceData data = deviceDataMap.computeIfAbsent(
|
DeviceData data = deviceDataMap.computeIfAbsent(
|
||||||
@@ -201,11 +210,13 @@ public class ConfigManager implements Runnable {
|
|||||||
for (ConfigListener listener : configListeners.values()) {
|
for (ConfigListener listener : configListeners.values()) {
|
||||||
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
||||||
}
|
}
|
||||||
|
// Check if there are requested updates for this zone
|
||||||
// Check event flag
|
String deviceZone =
|
||||||
|
deviceDataManager.getDeviceZone(device.serialNumber);
|
||||||
|
boolean isEvent = zonesToUpdateCopy.contains(deviceZone);
|
||||||
if (params.configOnEventOnly && !isEvent) {
|
if (params.configOnEventOnly && !isEvent) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Skipping config for {} (event flag not set)",
|
"Skipping config for {} (zone not marked for updates)",
|
||||||
device.serialNumber
|
device.serialNumber
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -251,15 +262,16 @@ public class ConfigManager implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty();
|
||||||
// Send config changes to devices
|
// Send config changes to devices
|
||||||
if (!params.configEnabled) {
|
if (!params.configEnabled) {
|
||||||
logger.trace("Config changes are disabled.");
|
logger.trace("Config changes are disabled.");
|
||||||
} else if (devicesNeedingUpdate.isEmpty()) {
|
} else if (devicesNeedingUpdate.isEmpty()) {
|
||||||
logger.debug("No device configs to send.");
|
logger.debug("No device configs to send.");
|
||||||
} else if (params.configOnEventOnly && !isEvent) {
|
} else if (params.configOnEventOnly && !shouldUpdate) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
logger.error(
|
logger.error(
|
||||||
"ERROR!! {} device(s) queued for config update, but event flag not set",
|
"ERROR!! {} device(s) queued for config update, but no zones queued for update.",
|
||||||
devicesNeedingUpdate.size()
|
devicesNeedingUpdate.size()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -317,10 +329,9 @@ public class ConfigManager implements Runnable {
|
|||||||
channelList.putAll(deviceConfig.userChannels);
|
channelList.putAll(deviceConfig.userChannels);
|
||||||
}
|
}
|
||||||
if (!channelList.isEmpty()) {
|
if (!channelList.isEmpty()) {
|
||||||
modified |= UCentralUtils.setRadioConfigField(
|
modified |= UCentralUtils.setRadioConfigChannel(
|
||||||
serialNumber,
|
serialNumber,
|
||||||
config,
|
config,
|
||||||
"channel",
|
|
||||||
channelList
|
channelList
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -334,10 +345,9 @@ public class ConfigManager implements Runnable {
|
|||||||
txPowerList.putAll(deviceConfig.userTxPowers);
|
txPowerList.putAll(deviceConfig.userTxPowers);
|
||||||
}
|
}
|
||||||
if (!txPowerList.isEmpty()) {
|
if (!txPowerList.isEmpty()) {
|
||||||
modified |= UCentralUtils.setRadioConfigField(
|
modified |= UCentralUtils.setRadioConfigTxPower(
|
||||||
serialNumber,
|
serialNumber,
|
||||||
config,
|
config,
|
||||||
"tx-power",
|
|
||||||
txPowerList
|
txPowerList
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -364,9 +374,38 @@ public class ConfigManager implements Runnable {
|
|||||||
return (configListeners.remove(id) != null);
|
return (configListeners.remove(id) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interrupt the main thread, possibly triggering an update immediately. */
|
/**
|
||||||
public void wakeUp() {
|
* Mark the zone to be updated, then interrupt the main thread to possibly
|
||||||
eventFlag.set(true);
|
* trigger an update immediately.
|
||||||
|
*
|
||||||
|
* @param zone non-null zone (i.e., venue)
|
||||||
|
*/
|
||||||
|
public void queueZoneAndWakeUp(String zone) {
|
||||||
|
if (zone == null) {
|
||||||
|
logger.debug("Zone to queue must be a non-null String.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zonesToUpdate.add(zone);
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track all zones to be updated, then interrupt the main thread to possibly
|
||||||
|
* trigger an update immediately.
|
||||||
|
*/
|
||||||
|
public void queueAllZonesAndWakeUp() {
|
||||||
|
/*
|
||||||
|
* Note, addAll is not atomic, but that is ok. This just means that it
|
||||||
|
* is possible that some zones may get updated now by the main thread
|
||||||
|
* while others get updated either when the main thread is woken up or
|
||||||
|
* the next time the main thread does its periodic update.
|
||||||
|
*/
|
||||||
|
zonesToUpdate.addAll(deviceDataManager.getZones());
|
||||||
|
wakeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Interrupt the main thread to possibly trigger an update immediately. */
|
||||||
|
private void wakeUp() {
|
||||||
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
||||||
wakeupFlag.set(true);
|
wakeupFlag.set(true);
|
||||||
mainThread.interrupt();
|
mainThread.interrupt();
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,22 +23,22 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.DataCollectorParams;
|
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.Utils;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||||
import com.facebook.openwifirrm.mysql.StateRecord;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
|
import com.facebook.openwifi.rrm.Utils;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.DataCollectorParams;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.rrm.mysql.StateRecord;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,23 +20,24 @@ import java.util.concurrent.LinkedBlockingQueue;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ModelerParams;
|
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.Utils;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
|
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ModelerParams;
|
||||||
|
import com.facebook.openwifi.rrm.Utils;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
@@ -93,15 +94,16 @@ public class Modeler implements Runnable {
|
|||||||
public Map<String, List<List<WifiScanEntry>>> latestWifiScans =
|
public Map<String, List<List<WifiScanEntry>>> latestWifiScans =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** List of latest state per device. */
|
/** List of latest states per device. */
|
||||||
public Map<String, State> latestState = new ConcurrentHashMap<>();
|
public Map<String, List<State>> latestStates =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** List of radio info per device. */
|
/** List of radio info per device. */
|
||||||
public Map<String, JsonArray> latestDeviceStatus =
|
public Map<String, List<UCentralSchema.Radio>> latestDeviceStatusRadios =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** List of capabilities per device. */
|
/** List of capabilities per device. */
|
||||||
public Map<String, JsonObject> latestDeviceCapabilities =
|
public Map<String, Map<String, Capabilities.Phy>> latestDeviceCapabilitiesPhy =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +269,10 @@ public class Modeler implements Runnable {
|
|||||||
if (state != null) {
|
if (state != null) {
|
||||||
try {
|
try {
|
||||||
State stateModel = gson.fromJson(state, State.class);
|
State stateModel = gson.fromJson(state, State.class);
|
||||||
dataModel.latestState.put(device.serialNumber, stateModel);
|
dataModel.latestStates.computeIfAbsent(
|
||||||
|
device.serialNumber,
|
||||||
|
k -> new LinkedList<>()
|
||||||
|
).add(stateModel);
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Device {}: added initial state from uCentralGw",
|
"Device {}: added initial state from uCentralGw",
|
||||||
device.serialNumber
|
device.serialNumber
|
||||||
@@ -299,8 +304,17 @@ public class Modeler implements Runnable {
|
|||||||
if (state != null) {
|
if (state != null) {
|
||||||
try {
|
try {
|
||||||
State stateModel = gson.fromJson(state, State.class);
|
State stateModel = gson.fromJson(state, State.class);
|
||||||
dataModel.latestState
|
List<State> latestStatesList = dataModel.latestStates
|
||||||
.put(record.serialNumber, stateModel);
|
.computeIfAbsent(
|
||||||
|
record.serialNumber,
|
||||||
|
k -> new LinkedList<>()
|
||||||
|
);
|
||||||
|
while (
|
||||||
|
latestStatesList.size() >= params.stateBufferSize
|
||||||
|
) {
|
||||||
|
latestStatesList.remove(0);
|
||||||
|
}
|
||||||
|
latestStatesList.add(stateModel);
|
||||||
stateUpdates.add(record.serialNumber);
|
stateUpdates.add(record.serialNumber);
|
||||||
} catch (JsonSyntaxException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -363,9 +377,9 @@ public class Modeler implements Runnable {
|
|||||||
String serialNumber,
|
String serialNumber,
|
||||||
DeviceCapabilities capabilities
|
DeviceCapabilities capabilities
|
||||||
) {
|
) {
|
||||||
dataModel.latestDeviceCapabilities.put(
|
dataModel.latestDeviceCapabilitiesPhy.put(
|
||||||
serialNumber,
|
serialNumber,
|
||||||
capabilities.capabilities.getAsJsonObject("wifi")
|
capabilities.capabilities.wifi
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,10 +391,11 @@ public class Modeler implements Runnable {
|
|||||||
UCentralApConfiguration config
|
UCentralApConfiguration config
|
||||||
) {
|
) {
|
||||||
// Get old vs new radios info and store the new radios info
|
// Get old vs new radios info and store the new radios info
|
||||||
JsonArray newRadioList = config.getRadioConfigList();
|
List<UCentralSchema.Radio> newRadioList = config.getRadioConfigList();
|
||||||
Set<String> newRadioBandsSet = config.getRadioBandsSet(newRadioList);
|
Set<String> newRadioBandsSet = config.getRadioBandsSet(newRadioList);
|
||||||
JsonArray oldRadioList = dataModel.latestDeviceStatus
|
List<UCentralSchema.Radio> oldRadioList =
|
||||||
.put(serialNumber, newRadioList);
|
dataModel.latestDeviceStatusRadios
|
||||||
|
.put(serialNumber, newRadioList);
|
||||||
Set<String> oldRadioBandsSet = config.getRadioBandsSet(oldRadioList);
|
Set<String> oldRadioBandsSet = config.getRadioBandsSet(oldRadioList);
|
||||||
|
|
||||||
// Print info only when there are any updates
|
// Print info only when there are any updates
|
||||||
@@ -423,19 +438,19 @@ public class Modeler implements Runnable {
|
|||||||
logger.debug("Removed some wifi scan entries from data model");
|
logger.debug("Removed some wifi scan entries from data model");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
dataModel.latestState.entrySet()
|
dataModel.latestStates.entrySet()
|
||||||
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
||||||
) {
|
) {
|
||||||
logger.debug("Removed some state entries from data model");
|
logger.debug("Removed some state entries from data model");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
dataModel.latestDeviceStatus.entrySet()
|
dataModel.latestDeviceStatusRadios.entrySet()
|
||||||
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
||||||
) {
|
) {
|
||||||
logger.debug("Removed some status entries from data model");
|
logger.debug("Removed some status entries from data model");
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
dataModel.latestDeviceCapabilities.entrySet()
|
dataModel.latestDeviceCapabilitiesPhy.entrySet()
|
||||||
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
||||||
) {
|
) {
|
||||||
logger.debug("Removed some capabilities entries from data model");
|
logger.debug("Removed some capabilities entries from data model");
|
||||||
@@ -6,24 +6,31 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.aggregators.Aggregator;
|
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||||
import com.facebook.openwifirrm.aggregators.MeanAggregator;
|
import com.facebook.openwifi.cloudsdk.AggregatedState;
|
||||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modeler utilities.
|
* Modeler utilities.
|
||||||
@@ -239,9 +246,9 @@ public class ModelerUtils {
|
|||||||
return Objects.equals(entry1.bssid, entry2.bssid) &&
|
return Objects.equals(entry1.bssid, entry2.bssid) &&
|
||||||
entry1.frequency == entry2.frequency &&
|
entry1.frequency == entry2.frequency &&
|
||||||
entry1.channel == entry2.channel &&
|
entry1.channel == entry2.channel &&
|
||||||
HTOperationElement
|
HTOperation
|
||||||
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
|
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
|
||||||
VHTOperationElement
|
VHTOperation
|
||||||
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
|
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +298,7 @@ public class ModelerUtils {
|
|||||||
/**
|
/**
|
||||||
* Compute aggregated wifiscans using a given reference time.
|
* Compute aggregated wifiscans using a given reference time.
|
||||||
*
|
*
|
||||||
* @see #getAggregatedWifiScans(com.facebook.openwifirrm.modules.Modeler.DataModel,
|
* @see #getAggregatedWifiScans(com.facebook.openwifi.rrm.modules.Modeler.DataModel,
|
||||||
* long, Aggregator)
|
* long, Aggregator)
|
||||||
*/
|
*/
|
||||||
public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans(
|
public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans(
|
||||||
@@ -381,4 +388,191 @@ public class ModelerUtils {
|
|||||||
}
|
}
|
||||||
return aggregatedWifiScans;
|
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,
|
||||||
|
Map<String, Capabilities.Phy> capabilityPhy
|
||||||
|
) {
|
||||||
|
if (radio.phy == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Capabilities.Phy phy = capabilityPhy.get(radio.phy);
|
||||||
|
if (phy == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] bands = phy.band;
|
||||||
|
if (bands == null || bands.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return bands[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -17,19 +18,19 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTag;
|
||||||
import com.facebook.openwifirrm.DeviceTopology;
|
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList;
|
||||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ProvMonitorParams;
|
import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList;
|
||||||
import com.facebook.openwifirrm.RRMSchedule;
|
import com.facebook.openwifi.cloudsdk.models.prov.Venue;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTag;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails;
|
import com.facebook.openwifi.rrm.DeviceTopology;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList;
|
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.Venue;
|
import com.facebook.openwifi.rrm.RRMSchedule;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ProvMonitorParams;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* owprov monitor module.
|
* owprov monitor module.
|
||||||
@@ -159,12 +160,21 @@ public class ProvMonitor implements Runnable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
RRMSchedule schedule = new RRMSchedule();
|
String[] crons = RRMScheduler
|
||||||
schedule.cron = RRMScheduler
|
|
||||||
.parseIntoQuartzCron(details.rrm.schedule);
|
.parseIntoQuartzCron(details.rrm.schedule);
|
||||||
if (schedule.cron == null || schedule.cron.isEmpty()) {
|
if (crons == null || crons.length == 0) {
|
||||||
return null;
|
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) {
|
if (details.rrm.algorithms != null) {
|
||||||
schedule.algorithms =
|
schedule.algorithms =
|
||||||
@@ -175,6 +185,7 @@ public class ProvMonitor implements Runnable {
|
|||||||
)
|
)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,17 +6,17 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifi.rrm.modules;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import org.quartz.CronScheduleBuilder;
|
|
||||||
import org.quartz.CronExpression;
|
import org.quartz.CronExpression;
|
||||||
|
import org.quartz.CronScheduleBuilder;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
import org.quartz.JobBuilder;
|
import org.quartz.JobBuilder;
|
||||||
import org.quartz.JobDetail;
|
import org.quartz.JobDetail;
|
||||||
@@ -32,10 +32,11 @@ import org.quartz.impl.StdSchedulerFactory;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
import com.facebook.openwifi.rrm.RRMSchedule;
|
||||||
|
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
@@ -74,15 +75,21 @@ public class RRMScheduler {
|
|||||||
/** The scheduler instance. */
|
/** The scheduler instance. */
|
||||||
private Scheduler scheduler;
|
private Scheduler scheduler;
|
||||||
|
|
||||||
/** The zones with active triggers scheduled. */
|
/**
|
||||||
private Set<String> scheduledZones;
|
* The job keys with active triggers scheduled. Job keys take the format of
|
||||||
|
* {@code <zone>:<index>}
|
||||||
|
*
|
||||||
|
* @see #parseIntoQuartzCron(String)
|
||||||
|
* */
|
||||||
|
private Set<String> scheduledJobKeys;
|
||||||
|
|
||||||
/** RRM job. */
|
/** RRM job. */
|
||||||
public static class RRMJob implements Job {
|
public static class RRMJob implements Job {
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context)
|
public void execute(JobExecutionContext context)
|
||||||
throws JobExecutionException {
|
throws JobExecutionException {
|
||||||
String zone = context.getTrigger().getKey().getName();
|
String jobKey = context.getTrigger().getKey().getName();
|
||||||
|
String zone = jobKey.split(":")[0];
|
||||||
logger.debug("Executing job for zone: {}", zone);
|
logger.debug("Executing job for zone: {}", zone);
|
||||||
try {
|
try {
|
||||||
SchedulerContext schedulerContext =
|
SchedulerContext schedulerContext =
|
||||||
@@ -107,13 +114,14 @@ public class RRMScheduler {
|
|||||||
* @param linuxCron Linux cron with seconds
|
* @param linuxCron Linux cron with seconds
|
||||||
* (seconds minutes hours day_of_month month day_of_week [year])
|
* (seconds minutes hours day_of_month month day_of_week [year])
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException when a linux cron cannot be parsed
|
* @throws IllegalArgumentException when a linux cron cannot be parsed into a
|
||||||
* into a valid Quartz spec
|
* valid Quartz spec
|
||||||
* @return String a Quartz supported cron
|
* @return String[] an array of length 1 or 2 of Quartz supported cron that's
|
||||||
|
* equivalent to the original linux cron
|
||||||
*/
|
*/
|
||||||
public static String parseIntoQuartzCron(String linuxCron) {
|
public static String[] parseIntoQuartzCron(String linuxCron) {
|
||||||
if (CronExpression.isValidExpression(linuxCron)) {
|
if (CronExpression.isValidExpression(linuxCron)) {
|
||||||
return linuxCron;
|
return new String[] { linuxCron };
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] split = linuxCron.split(" ");
|
String[] split = linuxCron.split(" ");
|
||||||
@@ -144,15 +152,36 @@ public class RRMScheduler {
|
|||||||
// if first case failed and only day of week is *, set to ?
|
// if first case failed and only day of week is *, set to ?
|
||||||
split[DAY_OF_WEEK_INDEX] = "?";
|
split[DAY_OF_WEEK_INDEX] = "?";
|
||||||
} else {
|
} else {
|
||||||
// Quartz does not support both values being set, so return null
|
// Quartz does not support both values being set but the standard says that
|
||||||
return null;
|
// 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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
String quartzCron = String.join(" ", split);
|
String quartzCron = String.join(" ", split);
|
||||||
if (!CronExpression.isValidExpression(quartzCron)) {
|
if (!CronExpression.isValidExpression(quartzCron)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return quartzCron;
|
|
||||||
|
return new String[] { quartzCron };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor. */
|
/** Constructor. */
|
||||||
@@ -194,7 +223,7 @@ public class RRMScheduler {
|
|||||||
// Schedule job and triggers
|
// Schedule job and triggers
|
||||||
scheduler.addJob(job, false);
|
scheduler.addJob(job, false);
|
||||||
syncTriggers();
|
syncTriggers();
|
||||||
logger.info("Scheduled {} RRM trigger(s)", scheduledZones.size());
|
logger.info("Scheduled {} RRM trigger(s)", scheduledJobKeys.size());
|
||||||
|
|
||||||
// Start scheduler
|
// Start scheduler
|
||||||
scheduler.start();
|
scheduler.start();
|
||||||
@@ -218,85 +247,98 @@ public class RRMScheduler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronize triggers to the current topology, adding/updating/deleting
|
* Synchronize triggers to the current topology, adding/updating/deleting
|
||||||
* them as necessary. This updates {@link #scheduledZones}.
|
* them as necessary. This updates {@link #scheduledJobKeys}.
|
||||||
*/
|
*/
|
||||||
public void syncTriggers() {
|
public void syncTriggers() {
|
||||||
Set<String> scheduled = ConcurrentHashMap.newKeySet();
|
Set<String> scheduled = ConcurrentHashMap.newKeySet();
|
||||||
Set<String> prevScheduled = new HashSet<>();
|
Set<String> prevScheduled = new HashSet<>();
|
||||||
if (scheduledZones != null) {
|
if (scheduledJobKeys != null) {
|
||||||
prevScheduled.addAll(scheduledZones);
|
prevScheduled.addAll(scheduledJobKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new triggers
|
// Add new triggers
|
||||||
for (String zone : deviceDataManager.getZones()) {
|
for (String zone : deviceDataManager.getZones()) {
|
||||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
||||||
|
RRMSchedule schedule = config.schedule;
|
||||||
if (
|
if (
|
||||||
config.schedule == null ||
|
schedule == null || schedule.crons == null ||
|
||||||
config.schedule.cron == null ||
|
schedule.crons.isEmpty()
|
||||||
config.schedule.cron.isEmpty()
|
|
||||||
) {
|
) {
|
||||||
continue; // RRM not scheduled
|
continue; // RRM not scheduled
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
for (int i = 0; i < schedule.crons.size(); i++) {
|
||||||
CronExpression.validateExpression(config.schedule.cron);
|
String cron = schedule.crons.get(i);
|
||||||
} catch (ParseException e) {
|
// if even one schedule has invalid cron, the whole thing is probably wrong
|
||||||
logger.error(
|
if (cron == null || cron.isEmpty()) {
|
||||||
String.format(
|
logger.error("There was an invalid cron in the schedule");
|
||||||
"Invalid cron expression (%s) for zone %s",
|
break;
|
||||||
config.schedule.cron,
|
}
|
||||||
zone
|
|
||||||
),
|
try {
|
||||||
e
|
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
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old triggers
|
// Remove old triggers
|
||||||
prevScheduled.removeAll(scheduled);
|
prevScheduled.removeAll(scheduled);
|
||||||
for (String zone : prevScheduled) {
|
for (String jobKey : prevScheduled) {
|
||||||
try {
|
try {
|
||||||
scheduler.unscheduleJob(TriggerKey.triggerKey(zone));
|
scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey));
|
||||||
} catch (SchedulerException e) {
|
} catch (SchedulerException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to remove RRM trigger for zone: " + zone,
|
"Failed to remove RRM trigger for jobKey: " + jobKey,
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
logger.debug("Removed RRM trigger for zone '{}'", zone);
|
logger.debug("Removed RRM trigger for jobKey '{}'", jobKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scheduledZones = scheduled;
|
this.scheduledJobKeys = scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run RRM algorithms for the given zone. */
|
/** Run RRM algorithms for the given zone. */
|
||||||
@@ -305,16 +347,19 @@ public class RRMScheduler {
|
|||||||
|
|
||||||
// Get algorithms from zone config
|
// Get algorithms from zone config
|
||||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
||||||
if (config.schedule == null) {
|
RRMSchedule schedule = config.schedule;
|
||||||
|
if (schedule == null) {
|
||||||
logger.error("RRM schedule missing for zone '{}', aborting!", zone);
|
logger.error("RRM schedule missing for zone '{}', aborting!", zone);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
config.schedule.algorithms == null ||
|
schedule.algorithms == null ||
|
||||||
config.schedule.algorithms.isEmpty()
|
schedule.algorithms.isEmpty()
|
||||||
) {
|
) {
|
||||||
logger.debug("Using default RRM algorithms for zone '{}'", zone);
|
logger
|
||||||
config.schedule.algorithms = Arrays.asList(
|
.debug("Using default RRM algorithms for zone '{}'", zone);
|
||||||
|
schedule.algorithms = Arrays.asList(
|
||||||
new RRMAlgorithm(
|
new RRMAlgorithm(
|
||||||
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
|
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
|
||||||
),
|
),
|
||||||
@@ -325,14 +370,15 @@ public class RRMScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute algorithms
|
// Execute algorithms
|
||||||
for (RRMAlgorithm algo : config.schedule.algorithms) {
|
for (RRMAlgorithm algo : schedule.algorithms) {
|
||||||
RRMAlgorithm.AlgorithmResult result = algo.run(
|
RRMAlgorithm.AlgorithmResult result = algo.run(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
params.dryRun,
|
params.dryRun,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
false /* updateImmediately */
|
||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
"'{}' result for zone '{}': {}",
|
"'{}' result for zone '{}': {}",
|
||||||
@@ -341,5 +387,6 @@ public class RRMScheduler {
|
|||||||
gson.toJson(result)
|
gson.toJson(result)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.mysql;
|
package com.facebook.openwifi.rrm.mysql;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
@@ -26,9 +26,9 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.Utils;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
import com.facebook.openwifi.rrm.Utils;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
@@ -360,7 +360,7 @@ public class DatabaseManager {
|
|||||||
/** Convert a list of state records to a State object. */
|
/** Convert a list of state records to a State object. */
|
||||||
private State toState(List<StateRecord> records, long ts) {
|
private State toState(List<StateRecord> records, long ts) {
|
||||||
State state = new State();
|
State state = new State();
|
||||||
state.unit = state.new Unit();
|
state.unit = new State.Unit();
|
||||||
state.unit.localtime = ts;
|
state.unit.localtime = ts;
|
||||||
|
|
||||||
// Parse each record
|
// Parse each record
|
||||||
@@ -454,9 +454,10 @@ public class DatabaseManager {
|
|||||||
.map(o -> gson.fromJson(o, State.Interface.class))
|
.map(o -> gson.fromJson(o, State.Interface.class))
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.toArray(new State.Interface[0]);
|
.toArray(new State.Interface[0]);
|
||||||
state.radios = new JsonObject[radios.lastKey() + 1];
|
state.radios = new State.Radio[radios.lastKey() + 1];
|
||||||
for (Map.Entry<Integer, JsonObject> entry : radios.entrySet()) {
|
for (Map.Entry<Integer, JsonObject> entry : radios.entrySet()) {
|
||||||
state.radios[entry.getKey()] = entry.getValue();
|
State.Radio radio = new State.Radio();
|
||||||
|
state.radios[entry.getKey()] = radio;
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.mysql;
|
package com.facebook.openwifi.rrm.mysql;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a record in the "state" table.
|
* Representation of a record in the "state" table.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.optimizers.channel;
|
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -18,16 +18,18 @@ import java.util.Map;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||||
|
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||||
|
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel optimizer base class.
|
* Channel optimizer base class.
|
||||||
@@ -39,24 +41,6 @@ public abstract class ChannelOptimizer {
|
|||||||
/** Minimum supported channel width (MHz), inclusive. */
|
/** Minimum supported channel width (MHz), inclusive. */
|
||||||
public static final int MIN_CHANNEL_WIDTH = 20;
|
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 */
|
/** Map of channel width (MHz) to available (primary) channels */
|
||||||
protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH =
|
protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH =
|
||||||
new HashMap<>();
|
new HashMap<>();
|
||||||
@@ -154,11 +138,11 @@ public abstract class ChannelOptimizer {
|
|||||||
// Remove model entries not in the given zone
|
// Remove model entries not in the given zone
|
||||||
this.model.latestWifiScans.keySet()
|
this.model.latestWifiScans.keySet()
|
||||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||||
this.model.latestState.keySet()
|
this.model.latestStates.keySet()
|
||||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||||
this.model.latestDeviceStatus.keySet()
|
this.model.latestDeviceStatusRadios.keySet()
|
||||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||||
this.model.latestDeviceCapabilities.keySet()
|
this.model.latestDeviceCapabilitiesPhy.keySet()
|
||||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +181,7 @@ public abstract class ChannelOptimizer {
|
|||||||
String vhtOper
|
String vhtOper
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
|
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
|
||||||
.contains(channel)
|
.contains(channel)
|
||||||
) {
|
) {
|
||||||
// 2.4G, it only supports 20 MHz
|
// 2.4G, it only supports 20 MHz
|
||||||
@@ -208,13 +192,13 @@ public abstract class ChannelOptimizer {
|
|||||||
return MIN_CHANNEL_WIDTH;
|
return MIN_CHANNEL_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
HTOperation htOperObj = new HTOperation(htOper);
|
||||||
if (vhtOper == null) {
|
if (vhtOper == null) {
|
||||||
// HT mode only supports 20/40 MHz
|
// HT mode only supports 20/40 MHz
|
||||||
return htOperObj.staChannelWidth ? 40 : 20;
|
return htOperObj.staChannelWidth ? 40 : 20;
|
||||||
} else {
|
} else {
|
||||||
// VHT/HE mode supports 20/40/160/80+80 MHz
|
// VHT/HE mode supports 20/40/160/80+80 MHz
|
||||||
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
|
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
|
||||||
if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) {
|
if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) {
|
||||||
return 20;
|
return 20;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -234,8 +218,9 @@ public abstract class ChannelOptimizer {
|
|||||||
// the difference of 8 means it is consecutive
|
// the difference of 8 means it is consecutive
|
||||||
int channelDiff =
|
int channelDiff =
|
||||||
Math.abs(vhtOperObj.channel1 - vhtOperObj.channel2);
|
Math.abs(vhtOperObj.channel1 - vhtOperObj.channel2);
|
||||||
// the "8080" below does not mean 8080 MHz wide, it refers to 80+80 MHz channel
|
// TODO it will currently return just 80 for 80p80 - it should be dealt
|
||||||
return channelDiff == 8 ? 160 : 8080;
|
// with properly.
|
||||||
|
return channelDiff == 8 ? 160 : 80;
|
||||||
} else {
|
} else {
|
||||||
return MIN_CHANNEL_WIDTH;
|
return MIN_CHANNEL_WIDTH;
|
||||||
}
|
}
|
||||||
@@ -315,25 +300,28 @@ public abstract class ChannelOptimizer {
|
|||||||
List<WifiScanEntry> scanRespsFiltered =
|
List<WifiScanEntry> scanRespsFiltered =
|
||||||
new ArrayList<WifiScanEntry>();
|
new ArrayList<WifiScanEntry>();
|
||||||
for (WifiScanEntry entry : scanResps) {
|
for (WifiScanEntry entry : scanResps) {
|
||||||
if (UCentralUtils.isChannelInBand(entry.channel, band)) {
|
final String entryBand = UCentralUtils
|
||||||
int channelWidth = getChannelWidthFromWiFiScan(
|
.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(
|
||||||
entry.channel,
|
entry.channel,
|
||||||
entry.ht_oper,
|
primaryChannel,
|
||||||
entry.vht_oper
|
channelWidth
|
||||||
);
|
);
|
||||||
int primaryChannel =
|
for (Integer newChannel : coveredChannels) {
|
||||||
getPrimaryChannel(entry.channel, channelWidth);
|
WifiScanEntry newEntry = new WifiScanEntry(entry);
|
||||||
List<Integer> coveredChannels =
|
newEntry.channel = newChannel;
|
||||||
getCoveredChannels(
|
scanRespsFiltered.add(newEntry);
|
||||||
entry.channel,
|
|
||||||
primaryChannel,
|
|
||||||
channelWidth
|
|
||||||
);
|
|
||||||
for (Integer newChannel : coveredChannels) {
|
|
||||||
WifiScanEntry newEntry = new WifiScanEntry(entry);
|
|
||||||
newEntry.channel = newChannel;
|
|
||||||
scanRespsFiltered.add(newEntry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,6 +349,7 @@ public abstract class ChannelOptimizer {
|
|||||||
* @param band the operational band (e.g., "2G")
|
* @param band the operational band (e.g., "2G")
|
||||||
* @param serialNumber the device's serial number
|
* @param serialNumber the device's serial number
|
||||||
* @param state the latest state of all the devices
|
* @param state the latest state of all the devices
|
||||||
|
* @param latestDeviceCapabilitiesPhy latest device phy from capabilities
|
||||||
* @return the current channel and channel width (MHz) of the device in the
|
* @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
|
* given band; returns a current channel of 0 if no channel in the given
|
||||||
* band is found.
|
* band is found.
|
||||||
@@ -368,7 +357,8 @@ public abstract class ChannelOptimizer {
|
|||||||
protected static int[] getCurrentChannel(
|
protected static int[] getCurrentChannel(
|
||||||
String band,
|
String band,
|
||||||
String serialNumber,
|
String serialNumber,
|
||||||
State state
|
State state,
|
||||||
|
Map<String, Map<String, Capabilities.Phy>> latestDeviceCapabilitiesPhy
|
||||||
) {
|
) {
|
||||||
int currentChannel = 0;
|
int currentChannel = 0;
|
||||||
int currentChannelWidth = MIN_CHANNEL_WIDTH;
|
int currentChannelWidth = MIN_CHANNEL_WIDTH;
|
||||||
@@ -378,16 +368,40 @@ public abstract class ChannelOptimizer {
|
|||||||
radioIndex < state.radios.length;
|
radioIndex < state.radios.length;
|
||||||
radioIndex++
|
radioIndex++
|
||||||
) {
|
) {
|
||||||
int tempChannel = state.radios[radioIndex]
|
State.Radio radio = state.radios[radioIndex];
|
||||||
.get("channel")
|
// check if radio is in band of interest
|
||||||
.getAsInt();
|
Map<String, Capabilities.Phy> capabilitiesPhy =
|
||||||
if (UCentralUtils.isChannelInBand(tempChannel, band)) {
|
latestDeviceCapabilitiesPhy.get(serialNumber);
|
||||||
currentChannel = tempChannel;
|
if (capabilitiesPhy == null) {
|
||||||
currentChannelWidth = state.radios[radioIndex]
|
continue;
|
||||||
.get("channel_width")
|
}
|
||||||
.getAsInt();
|
final String radioBand = ModelerUtils.getBand(
|
||||||
|
radio,
|
||||||
|
capabilitiesPhy
|
||||||
|
);
|
||||||
|
if (radioBand == null || !radioBand.equals(band)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
if (parsedChannelWidth != null) {
|
||||||
|
currentChannelWidth = parsedChannelWidth;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
"Invalid channel width {}",
|
||||||
|
radio.channel_width
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return new int[] { currentChannel, currentChannelWidth };
|
return new int[] { currentChannel, currentChannelWidth };
|
||||||
}
|
}
|
||||||
@@ -627,14 +641,13 @@ public abstract class ChannelOptimizer {
|
|||||||
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program the given channel map into the AP config and notify the config
|
* Program the given channel map into the AP config.
|
||||||
* manager.
|
|
||||||
*
|
*
|
||||||
* @param deviceDataManager the DeviceDataManager instance
|
* @param deviceDataManager the DeviceDataManager instance
|
||||||
* @param configManager the ConfigManager instance
|
* @param configManager the ConfigManager instance
|
||||||
* @param channelMap the map of devices (by serial number) to radio to channel
|
* @param channelMap the map of devices (by serial number) to radio to channel
|
||||||
*/
|
*/
|
||||||
public void applyConfig(
|
public void updateDeviceApConfig(
|
||||||
DeviceDataManager deviceDataManager,
|
DeviceDataManager deviceDataManager,
|
||||||
ConfigManager configManager,
|
ConfigManager configManager,
|
||||||
Map<String, Map<String, Integer>> channelMap
|
Map<String, Map<String, Integer>> channelMap
|
||||||
@@ -652,8 +665,5 @@ public abstract class ChannelOptimizer {
|
|||||||
deviceConfig.autoChannels = entry.getValue();
|
deviceConfig.autoChannels = entry.getValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger config update now
|
|
||||||
configManager.wakeUp();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.facebook.openwifirrm.optimizers.channel;
|
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -20,12 +20,13 @@ import java.util.stream.Collectors;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||||
|
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Least used channel optimizer.
|
* Least used channel optimizer.
|
||||||
@@ -89,13 +90,13 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
|||||||
protected static Map<Integer, Integer> getOccupiedOverlapChannels(
|
protected static Map<Integer, Integer> getOccupiedOverlapChannels(
|
||||||
Map<Integer, Integer> occupiedChannels
|
Map<Integer, Integer> occupiedChannels
|
||||||
) {
|
) {
|
||||||
int maxChannel =
|
final int maxChannel =
|
||||||
UCentralUtils.UPPER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
UCentralUtils.getUpperChannelLimit(UCentralConstants.BAND_2G);
|
||||||
int minChannel =
|
final int minChannel =
|
||||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
UCentralUtils.getLowerChannelLimit(UCentralConstants.BAND_2G);
|
||||||
Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>();
|
Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>();
|
||||||
for (
|
for (
|
||||||
int overlapChannel : AVAILABLE_CHANNELS_BAND
|
int overlapChannel : UCentralUtils.AVAILABLE_CHANNELS_BAND
|
||||||
.get(UCentralConstants.BAND_2G)
|
.get(UCentralConstants.BAND_2G)
|
||||||
) {
|
) {
|
||||||
int occupancy = 0;
|
int occupancy = 0;
|
||||||
@@ -331,17 +332,19 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
|||||||
public Map<String, Map<String, Integer>> computeChannelMap() {
|
public Map<String, Map<String, Integer>> computeChannelMap() {
|
||||||
Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
|
Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
|
||||||
Map<String, List<String>> bandsMap = UCentralUtils
|
Map<String, List<String>> bandsMap = UCentralUtils
|
||||||
.getBandsMap(model.latestDeviceStatus);
|
.getBandsMap(model.latestDeviceStatusRadios);
|
||||||
|
|
||||||
Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
|
Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
|
||||||
UCentralUtils.getDeviceAvailableChannels(
|
UCentralUtils.getDeviceAvailableChannels(
|
||||||
model.latestDeviceStatus,
|
model.latestDeviceStatusRadios,
|
||||||
model.latestDeviceCapabilities,
|
model.latestDeviceCapabilitiesPhy,
|
||||||
AVAILABLE_CHANNELS_BAND
|
UCentralUtils.AVAILABLE_CHANNELS_BAND
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Map<String, State> latestState =
|
||||||
|
ModelerUtils.getLatestState(model.latestStates);
|
||||||
Map<String, String> bssidsMap =
|
Map<String, String> bssidsMap =
|
||||||
UCentralUtils.getBssidsMap(model.latestState);
|
UCentralUtils.getBssidsMap(latestState);
|
||||||
|
|
||||||
for (String band : bandsMap.keySet()) {
|
for (String band : bandsMap.keySet()) {
|
||||||
// Performance metrics
|
// Performance metrics
|
||||||
@@ -369,11 +372,12 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
|||||||
availableChannelsList == null ||
|
availableChannelsList == null ||
|
||||||
availableChannelsList.isEmpty()
|
availableChannelsList.isEmpty()
|
||||||
) {
|
) {
|
||||||
availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
|
availableChannelsList =
|
||||||
|
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current channel of the device
|
// Get current channel of the device
|
||||||
State state = model.latestState.get(serialNumber);
|
State state = latestState.get(serialNumber);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Device {}: No state found, skipping...",
|
"Device {}: No state found, skipping...",
|
||||||
@@ -389,7 +393,12 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int[] currentChannelInfo =
|
int[] currentChannelInfo =
|
||||||
getCurrentChannel(band, serialNumber, state);
|
getCurrentChannel(
|
||||||
|
band,
|
||||||
|
serialNumber,
|
||||||
|
state,
|
||||||
|
model.latestDeviceCapabilitiesPhy
|
||||||
|
);
|
||||||
int currentChannel = currentChannelInfo[0];
|
int currentChannel = currentChannelInfo[0];
|
||||||
// Filter out APs if the radios in the state do not contain a
|
// 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
|
// channel in a band given by the state. This can happen when
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user