Files
wlan-cloud-rrm/src/test/java/com/facebook/openwifirrm/modules/ApiServerTest.java
2022-06-02 17:16:30 -07:00

464 lines
15 KiB
Java

/*
* 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.openwifirrm.modules;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
import java.util.HashMap;
import java.util.TreeSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestMethodOrder;
import com.facebook.openwifirrm.DeviceConfig;
import com.facebook.openwifirrm.DeviceDataManager;
import com.facebook.openwifirrm.DeviceLayeredConfig;
import com.facebook.openwifirrm.DeviceTopology;
import com.facebook.openwifirrm.RRMConfig;
import com.facebook.openwifirrm.mysql.DatabaseManager;
import com.facebook.openwifirrm.ucentral.UCentralClient;
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
import com.google.gson.Gson;
import kong.unirest.HttpResponse;
import kong.unirest.JsonNode;
import kong.unirest.Unirest;
import kong.unirest.json.JSONObject;
import spark.Spark;
@TestMethodOrder(OrderAnnotation.class)
public class ApiServerTest {
/** The test server port. */
private static final int TEST_PORT = spark.Service.SPARK_DEFAULT_PORT;
/** Test device data manager. */
private DeviceDataManager deviceDataManager;
/** Test RRM config. */
private RRMConfig rrmConfig;
/** Test API server instance. */
private ApiServer server;
/** Test modeler instance. */
private Modeler modeler;
/** The Gson instance. */
private final Gson gson = new Gson();
/** Build an endpoint URL. */
private String endpoint(String path) {
return String.format("http://localhost:%d%s", TEST_PORT, path);
}
@BeforeEach
void setup(TestInfo testInfo) {
this.deviceDataManager = new DeviceDataManager();
// Create config
this.rrmConfig = new RRMConfig();
rrmConfig.moduleConfig.apiServerParams.httpPort = TEST_PORT;
rrmConfig.moduleConfig.apiServerParams.useBasicAuth = false;
rrmConfig.moduleConfig.apiServerParams.basicAuthUser = "";
rrmConfig.moduleConfig.apiServerParams.basicAuthPassword = "";
// Create clients (null for now)
UCentralClient client = null;
UCentralKafkaConsumer consumer = null;
DatabaseManager dbManager = null;
// Instantiate dependent instances
ConfigManager configManager = new ConfigManager(
rrmConfig.moduleConfig.configManagerParams,
deviceDataManager,
client
);
DataCollector dataCollector = new DataCollector(
rrmConfig.moduleConfig.dataCollectorParams,
deviceDataManager,
client,
consumer,
configManager,
dbManager
);
this.modeler = new Modeler(
rrmConfig.moduleConfig.modelerParams,
deviceDataManager,
consumer,
client,
dataCollector,
configManager
);
// Instantiate ApiServer
this.server = new ApiServer(
rrmConfig.moduleConfig.apiServerParams,
deviceDataManager,
configManager,
modeler
);
try {
server.run();
Spark.awaitInitialization();
} catch (Exception e) {
fail("Could not instantiate ApiServer.", e);
}
}
@AfterEach
void tearDown() {
// Destroy ApiServer
if (server != null) {
server.shutdown();
Spark.awaitStop();
}
server = null;
// Reset Unirest client
// Without this, Unirest randomly throws:
// kong.unirest.UnirestException: java.net.SocketException: Software caused connection abort: recv failed
Unirest.shutDown();
}
@Test
@Order(1)
void test_getTopology() throws Exception {
// Create topology
DeviceTopology topology = new DeviceTopology();
topology.put("test-zone", new TreeSet<>(Arrays.asList("aaaaaaaaaaa")));
deviceDataManager.setTopology(topology);
// Fetch topology
HttpResponse<String> resp = Unirest.get(endpoint("/api/v1/getTopology")).asString();
assertEquals(200, resp.getStatus());
assertEquals(deviceDataManager.getTopologyJson(), resp.getBody());
}
@Test
@Order(2)
void test_setTopology() throws Exception {
String url = endpoint("/api/v1/setTopology");
// Create topology
DeviceTopology topology = new DeviceTopology();
topology.put("test-zone", new TreeSet<>(Arrays.asList("aaaaaaaaaaa")));
// Set topology
HttpResponse<String> resp = Unirest
.post(url)
.body(gson.toJson(topology))
.asString();
assertEquals(200, resp.getStatus());
assertEquals(gson.toJson(topology), deviceDataManager.getTopologyJson());
// Missing/wrong parameters
assertEquals(400, Unirest.post(url).body("not json").asString().getStatus());
}
@Test
@Order(3)
void test_getDeviceLayeredConfig() throws Exception {
// Create topology and configs
final String zone = "test-zone";
final String ap = "aaaaaaaaaaa";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList(ap)));
deviceDataManager.setTopology(topology);
DeviceLayeredConfig cfg = new DeviceLayeredConfig();
cfg.networkConfig.enableRRM = true;
DeviceConfig zoneConfig = new DeviceConfig();
zoneConfig.enableWifiScan = false;
cfg.zoneConfig.put(zone, zoneConfig);
DeviceConfig apConfig = new DeviceConfig();
apConfig.enableConfig = false;
cfg.apConfig.put(ap, apConfig);
deviceDataManager.setDeviceApConfig(ap, apConfig);
// Fetch config
HttpResponse<String> resp = Unirest.get(endpoint("/api/v1/getDeviceLayeredConfig")).asString();
assertEquals(200, resp.getStatus());
assertEquals(deviceDataManager.getDeviceLayeredConfigJson(), resp.getBody());
}
@Test
@Order(4)
void test_getDeviceConfig() throws Exception {
String url = endpoint("/api/v1/getDeviceConfig");
// Create topology
final String zone = "test-zone";
final String ap = "aaaaaaaaaaa";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList(ap)));
deviceDataManager.setTopology(topology);
// Fetch config
HttpResponse<String> resp = Unirest.get(url + "?serial=" + ap).asString();
assertEquals(200, resp.getStatus());
String normalizedResp = gson.toJson(gson.fromJson(resp.getBody(), DeviceConfig.class));
assertEquals(gson.toJson(deviceDataManager.getDeviceConfig(ap)), normalizedResp);
// Missing/wrong parameters
assertEquals(400, Unirest.get(url).asString().getStatus());
assertEquals(400, Unirest.get(url + "?serial=asdf").asString().getStatus());
}
@Test
@Order(5)
void test_setDeviceNetworkConfig() throws Exception {
DeviceConfig config = new DeviceConfig();
config.enableConfig = false;
// Set config
HttpResponse<String> resp = Unirest
.post(endpoint("/api/v1/setDeviceNetworkConfig"))
.body(gson.toJson(config))
.asString();
assertEquals(200, resp.getStatus());
DeviceLayeredConfig fullCfg = gson.fromJson(
deviceDataManager.getDeviceLayeredConfigJson(),
DeviceLayeredConfig.class
);
assertEquals(gson.toJson(config), gson.toJson(fullCfg.networkConfig));
}
@Test
@Order(6)
void test_setDeviceZoneConfig() throws Exception {
String url = endpoint("/api/v1/setDeviceZoneConfig");
// Create topology
final String zone = "test-zone";
final String ap = "aaaaaaaaaaa";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList(ap)));
deviceDataManager.setTopology(topology);
DeviceConfig config = new DeviceConfig();
config.enableConfig = false;
// Set config
HttpResponse<String> resp = Unirest
.post(url + "?zone=" + zone)
.body(gson.toJson(config))
.asString();
assertEquals(200, resp.getStatus());
DeviceLayeredConfig fullCfg = gson.fromJson(
deviceDataManager.getDeviceLayeredConfigJson(),
DeviceLayeredConfig.class
);
assertEquals(1, fullCfg.zoneConfig.size());
assertTrue(fullCfg.zoneConfig.containsKey(zone));
assertEquals(gson.toJson(config), gson.toJson(fullCfg.zoneConfig.get(zone)));
// Missing/wrong parameters
assertEquals(400, Unirest.post(url).body(gson.toJson(config)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?zone=asdf").body(gson.toJson(config)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?zone=" + zone).body("not json").asString().getStatus());
}
@Test
@Order(7)
void test_setDeviceApConfig() throws Exception {
String url = endpoint("/api/v1/setDeviceApConfig");
// Create topology
final String zone = "test-zone";
final String ap = "aaaaaaaaaaa";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList(ap)));
deviceDataManager.setTopology(topology);
DeviceConfig config = new DeviceConfig();
config.enableConfig = false;
// Set config
HttpResponse<String> resp = Unirest
.post(url + "?serial=" + ap)
.body(gson.toJson(config))
.asString();
assertEquals(200, resp.getStatus());
DeviceLayeredConfig fullCfg = gson.fromJson(
deviceDataManager.getDeviceLayeredConfigJson(),
DeviceLayeredConfig.class
);
assertEquals(1, fullCfg.apConfig.size());
assertTrue(fullCfg.apConfig.containsKey(ap));
assertEquals(gson.toJson(config), gson.toJson(fullCfg.apConfig.get(ap)));
// Missing/wrong parameters
assertEquals(400, Unirest.post(url).body(gson.toJson(config)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?serial=asdf").body(gson.toJson(config)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?serial=" + ap).body("not json").asString().getStatus());
}
@Test
@Order(8)
void test_modifyDeviceApConfig() throws Exception {
String url = endpoint("/api/v1/modifyDeviceApConfig");
// Create topology
final String zone = "test-zone";
final String ap = "aaaaaaaaaaa";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList(ap)));
deviceDataManager.setTopology(topology);
// Set initial AP config
DeviceConfig apConfig = new DeviceConfig();
apConfig.enableConfig = false;
apConfig.autoChannels = new HashMap<>();
apConfig.autoChannels.put("2G", 7);
apConfig.autoChannels.put("5G", 165);
deviceDataManager.setDeviceApConfig(ap, apConfig);
// Construct config request
DeviceConfig configReq = new DeviceConfig();
configReq.enableConfig = true;
configReq.autoTxPowers = new HashMap<>();
configReq.autoTxPowers.put("2G", 20);
configReq.autoTxPowers.put("5G", 28);
// Merge config objects (expected result)
apConfig.apply(configReq);
// Set config
HttpResponse<String> resp = Unirest
.post(url + "?serial=" + ap)
.body(gson.toJson(configReq))
.asString();
assertEquals(200, resp.getStatus());
DeviceLayeredConfig fullCfg = gson.fromJson(
deviceDataManager.getDeviceLayeredConfigJson(),
DeviceLayeredConfig.class
);
assertTrue(fullCfg.apConfig.containsKey(ap));
assertEquals(gson.toJson(apConfig), gson.toJson(fullCfg.apConfig.get(ap)));
// Missing/wrong parameters
assertEquals(400, Unirest.post(url).body(gson.toJson(configReq)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?serial=asdf").body(gson.toJson(configReq)).asString().getStatus());
assertEquals(400, Unirest.post(url + "?serial=" + ap).body("not json").asString().getStatus());
assertEquals(400, Unirest.post(url + "?serial=" + ap).body("{}").asString().getStatus());
}
@Test
@Order(100)
void test_currentModel() throws Exception {
// Fetch RRM model
HttpResponse<String> resp = Unirest.get(endpoint("/api/v1/currentModel")).asString();
assertEquals(200, resp.getStatus());
assertEquals(gson.toJson(modeler.getDataModel()), resp.getBody());
}
@Test
@Order(101)
void test_optimizeChannel() throws Exception {
String url = endpoint("/api/v1/optimizeChannel");
// Create topology
final String zone = "test-zone";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList("aaaaaaaaaaa")));
deviceDataManager.setTopology(topology);
// Correct requests
final String[] modes = new String[] { "random", "least_used", "unmanaged_aware" };
for (String mode : modes) {
String endpoint = String.format("%s?mode=%s&zone=%s", url, mode, zone);
HttpResponse<JsonNode> simpleResp = Unirest.get(endpoint).asJson();
assertEquals(200, simpleResp.getStatus());
assertNotNull(simpleResp.getBody().getObject().getJSONObject("data"));
}
// Missing/wrong parameters
assertEquals(400, Unirest.get(url).asString().getStatus());
assertEquals(400, Unirest.get(url + "?mode=test123&zone=" + zone).asString().getStatus());
assertEquals(400, Unirest.get(url + "?zone=asdf&mode=" + modes[0]).asString().getStatus());
}
@Test
@Order(102)
void test_optimizeTxPower() throws Exception {
String url = endpoint("/api/v1/optimizeTxPower");
// Create topology
final String zone = "test-zone";
DeviceTopology topology = new DeviceTopology();
topology.put(zone, new TreeSet<>(Arrays.asList("aaaaaaaaaaa")));
deviceDataManager.setTopology(topology);
// Correct requests
final String[] modes = new String[] { "random", "measure_ap_client", "measure_ap_ap", "location_optimal" };
for (String mode : modes) {
String endpoint = String.format("%s?mode=%s&zone=%s", url, mode, zone);
HttpResponse<JsonNode> simpleResp = Unirest.get(endpoint).asJson();
assertEquals(200, simpleResp.getStatus());
assertNotNull(simpleResp.getBody().getObject().getJSONObject("data"));
}
// Missing/wrong parameters
assertEquals(400, Unirest.get(url).asString().getStatus());
assertEquals(400, Unirest.get(url + "?mode=test123&zone=" + zone).asString().getStatus());
assertEquals(400, Unirest.get(url + "?zone=asdf&mode=" + modes[0]).asString().getStatus());
}
@Test
@Order(1000)
void testDocs() throws Exception {
// Index page paths
assertEquals(200, Unirest.get(endpoint("/")).asString().getStatus());
assertEquals(200, Unirest.get(endpoint("/index.html")).asString().getStatus());
// OpenAPI YAML/JSON
HttpResponse<String> yamlResp = Unirest.get(endpoint("/openapi.yaml")).asString();
assertEquals(200, yamlResp.getStatus());
HttpResponse<JsonNode> jsonResp = Unirest.get(endpoint("/openapi.json")).asJson();
assertEquals(200, jsonResp.getStatus());
// Check that we got the OpenAPI 3.x version string
assertTrue(
Arrays.stream(
yamlResp.getBody().split("\\R+")
).filter(line -> line.matches("^openapi: 3[0-9.]*$"))
.findFirst()
.isPresent()
);
assertTrue(
jsonResp.getBody().getObject().getString("openapi").matches("^3[0-9.]*$")
);
// Check that we got some endpoint paths
JSONObject paths = jsonResp.getBody().getObject().getJSONObject("paths");
assertFalse(paths.isEmpty());
assertTrue(paths.keys().next().startsWith("/api/"));
}
@Test
@Order(1001)
void test404() throws Exception {
final String fakeEndpoint = endpoint("/test123");
assertEquals(404, Unirest.get(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.post(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.put(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.delete(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.options(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.head(fakeEndpoint).asString().getStatus());
assertEquals(404, Unirest.patch(fakeEndpoint).asString().getStatus());
}
}