mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-11-01 19:17:53 +00:00
Add script API and some related utilities (#109)
This commit is contained in:
@@ -25,6 +25,7 @@ import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
|||||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest;
|
||||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus;
|
||||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||||
|
import com.facebook.openwifi.cloudsdk.models.gw.ScriptRequest;
|
||||||
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||||
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
|
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
|
||||||
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||||
@@ -562,6 +563,83 @@ public class UCentralClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a shell script on a device and return the result, or null upon error.
|
||||||
|
*
|
||||||
|
* @see #runScript(String, String, int)
|
||||||
|
*/
|
||||||
|
public CommandInfo runScript(String serialNumber, String script) {
|
||||||
|
return runScript(serialNumber, script, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a shell script on a device and return the result, or null upon error.
|
||||||
|
*
|
||||||
|
* @see #runScript(String, String, int, String)
|
||||||
|
*/
|
||||||
|
public CommandInfo runScript(
|
||||||
|
String serialNumber,
|
||||||
|
String script,
|
||||||
|
int timeoutSec
|
||||||
|
) {
|
||||||
|
return runScript(serialNumber, script, timeoutSec, "shell");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a script on a device and return the result, or null upon error.
|
||||||
|
*
|
||||||
|
* @param serialNumber the device
|
||||||
|
* @param script the script contents
|
||||||
|
* @param timeoutSec the timeout in seconds
|
||||||
|
* @param type the script type (either "shell" or "ucode")
|
||||||
|
*
|
||||||
|
* @see UCentralUtils#getScriptOutput(CommandInfo)
|
||||||
|
*/
|
||||||
|
public CommandInfo runScript(
|
||||||
|
String serialNumber,
|
||||||
|
String script,
|
||||||
|
int timeoutSec,
|
||||||
|
String type
|
||||||
|
) {
|
||||||
|
ScriptRequest req = new ScriptRequest();
|
||||||
|
req.serialNumber = serialNumber;
|
||||||
|
req.timeout = timeoutSec;
|
||||||
|
req.type = type;
|
||||||
|
req.script = script;
|
||||||
|
req.scriptId = "1"; // ??
|
||||||
|
HttpResponse<String> response = httpPost(
|
||||||
|
String.format("device/%s/script", serialNumber),
|
||||||
|
OWGW_SERVICE,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
if (!response.isSuccess()) {
|
||||||
|
logger.error("Error: {}", response.getBody());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return gson.fromJson(response.getBody(), CommandInfo.class);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
String errMsg = String.format(
|
||||||
|
"Failed to deserialize to CommandInfo: %s",
|
||||||
|
response.getBody()
|
||||||
|
);
|
||||||
|
logger.error(errMsg, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instruct a device (AP) to ping a given destination (IP/hostname),
|
||||||
|
* returning the raw ping output or null upon error.
|
||||||
|
*/
|
||||||
|
public String pingFromDevice(String serialNumber, String host) {
|
||||||
|
// TODO pass options, parse output
|
||||||
|
final int PING_COUNT = 5;
|
||||||
|
String script = String.format("ping -c %d %s", PING_COUNT, host);
|
||||||
|
CommandInfo info = runScript(serialNumber, script);
|
||||||
|
return UCentralUtils.getScriptOutput(info);
|
||||||
|
}
|
||||||
|
|
||||||
/** Retrieve a list of inventory from owprov. */
|
/** Retrieve a list of inventory from owprov. */
|
||||||
public InventoryTagList getProvInventory() {
|
public InventoryTagList getProvInventory() {
|
||||||
HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE);
|
HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@
|
|||||||
|
|
||||||
package com.facebook.openwifi.cloudsdk;
|
package com.facebook.openwifi.cloudsdk;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -17,6 +19,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.zip.DataFormatException;
|
||||||
|
import java.util.zip.Inflater;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -27,6 +31,7 @@ import com.facebook.openwifi.cloudsdk.ies.LocalPowerConstraint;
|
|||||||
import com.facebook.openwifi.cloudsdk.ies.QbssLoad;
|
import com.facebook.openwifi.cloudsdk.ies.QbssLoad;
|
||||||
import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo;
|
import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo;
|
||||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||||
|
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
|
||||||
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;
|
||||||
@@ -477,4 +482,111 @@ public class UCentralUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a map of Wi-Fi client (STA) MAC addresses to the Client structure
|
||||||
|
* found for that interface. This does NOT support clients connected on
|
||||||
|
* multiple interfaces simultaneously.
|
||||||
|
*/
|
||||||
|
public static Map<String, State.Interface.Client> getWifiClientInfo(
|
||||||
|
State state
|
||||||
|
) {
|
||||||
|
Map<String, State.Interface.Client> ret = new HashMap<>();
|
||||||
|
|
||||||
|
// Aggregate over all interfaces
|
||||||
|
for (State.Interface iface : state.interfaces) {
|
||||||
|
if (iface.ssids == null || iface.clients == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert client array to map (for faster lookups)
|
||||||
|
Map<String, State.Interface.Client> ifaceMap = new HashMap<>();
|
||||||
|
for (State.Interface.Client client : iface.clients) {
|
||||||
|
ifaceMap.put(client.mac, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all SSIDs and connected clients
|
||||||
|
for (State.Interface.SSID ssid : iface.ssids) {
|
||||||
|
if (ssid.associations == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
State.Interface.SSID.Association association : ssid.associations
|
||||||
|
) {
|
||||||
|
State.Interface.Client client =
|
||||||
|
ifaceMap.get(association.station);
|
||||||
|
if (client != null) {
|
||||||
|
ret.put(association.station, client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decompress (inflate) a UTF-8 string using ZLIB.
|
||||||
|
*
|
||||||
|
* @param compressed the compressed string
|
||||||
|
* @param uncompressedSize the uncompressed size (must be known)
|
||||||
|
*/
|
||||||
|
private static String inflate(String compressed, int uncompressedSize)
|
||||||
|
throws DataFormatException {
|
||||||
|
if (compressed == null) {
|
||||||
|
throw new NullPointerException("Null compressed string");
|
||||||
|
}
|
||||||
|
if (uncompressedSize < 0) {
|
||||||
|
throw new IllegalArgumentException("Invalid size");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] input = compressed.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] output = new byte[uncompressedSize];
|
||||||
|
|
||||||
|
Inflater inflater = new Inflater();
|
||||||
|
inflater.setInput(input, 0, input.length);
|
||||||
|
inflater.inflate(output);
|
||||||
|
inflater.end();
|
||||||
|
|
||||||
|
return new String(output, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the result of the "script" API, return the actual script output
|
||||||
|
* (decoded/decompressed if needed), or null if the script returned an
|
||||||
|
* error.
|
||||||
|
*
|
||||||
|
* @see UCentralClient#runScript(String, String, int, String)
|
||||||
|
*/
|
||||||
|
public static String getScriptOutput(CommandInfo info) {
|
||||||
|
if (info == null || info.results == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!info.results.has("status")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JsonObject status = info.results.get("status").getAsJsonObject();
|
||||||
|
if (!status.has("error") || status.get("error").getAsInt() != 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (status.has("result")) {
|
||||||
|
// Raw result
|
||||||
|
return status.get("result").getAsString();
|
||||||
|
} else if (status.has("result_64") && status.has("result_sz")) {
|
||||||
|
// Base64+compressed result
|
||||||
|
// NOTE: untested, not actually implemented on ucentral-client?
|
||||||
|
try {
|
||||||
|
String encoded = status.get("result_64").getAsString();
|
||||||
|
int uncompressedSize = status.get("result_sz").getAsInt();
|
||||||
|
String decoded = new String(
|
||||||
|
Base64.getDecoder().decode(encoded),
|
||||||
|
StandardCharsets.UTF_8
|
||||||
|
);
|
||||||
|
return inflate(decoded, uncompressedSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Failed to decode or inflate script result", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ public class State {
|
|||||||
public int ack_signal;
|
public int ack_signal;
|
||||||
public int ack_signal_avg;
|
public int ack_signal_avg;
|
||||||
public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats
|
public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats
|
||||||
|
|
||||||
|
// TODO ipaddr_v4 - either string or object (ip4leases), but duplicated in "clients"
|
||||||
}
|
}
|
||||||
|
|
||||||
public Association[] associations;
|
public Association[] associations;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* 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.gw;
|
||||||
|
|
||||||
|
public class ScriptRequest {
|
||||||
|
public String serialNumber;
|
||||||
|
public long timeout = 30; // in seconds
|
||||||
|
public String type; // "shell", "ucode", "uci"
|
||||||
|
public String script;
|
||||||
|
public String scriptId; // required but unused?
|
||||||
|
public long when = 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user