mirror of
				https://github.com/Telecominfraproject/wlan-cloud-rrm.git
				synced 2025-10-30 02:02:28 +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.DeviceListWithStatus; | ||||
| 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.StatisticsRecords; | ||||
| 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. */ | ||||
| 	public InventoryTagList getProvInventory() { | ||||
| 		HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE); | ||||
|   | ||||
| @@ -8,8 +8,10 @@ | ||||
|  | ||||
| package com.facebook.openwifi.cloudsdk; | ||||
|  | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Base64; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.HashSet; | ||||
| @@ -17,6 +19,8 @@ import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Map.Entry; | ||||
| import java.util.Set; | ||||
| import java.util.zip.DataFormatException; | ||||
| import java.util.zip.Inflater; | ||||
|  | ||||
| import org.slf4j.Logger; | ||||
| 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.TxPwrInfo; | ||||
| 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.JsonArray; | ||||
| import com.google.gson.JsonElement; | ||||
| @@ -477,4 +482,111 @@ public class UCentralUtils { | ||||
| 			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_avg; | ||||
| 				public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats | ||||
|  | ||||
| 				// TODO ipaddr_v4 - either string or object (ip4leases), but duplicated in "clients" | ||||
| 			} | ||||
|  | ||||
| 			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
	 Jeffrey Han
					Jeffrey Han