diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/bin/wifi_test.py b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/bin/wifi_test.py new file mode 100755 index 0000000000..4d468fff97 --- /dev/null +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/bin/wifi_test.py @@ -0,0 +1,386 @@ +#!/usr/bin/env python3 +""" + This script base on network-manager. And network-manager has it own + limitation about wifi ap mode and wifi-p2p. + For wifi ap mode: + Only band a and bg are supported + + For wifi-p2p: + We are not able to validate the result even following the user + manual of network-manager. + + Please refer to following for more details: + [1] https://networkmanager.dev/docs/api/latest/nm-settings-nmcli.html + [2] https://netplan.readthedocs.io/en/stable/netplan-yaml + [3] https://bugs.launchpad.net/carmel/+bug/2080353/comments/2 + +""" +import argparse +import subprocess +import sys +import time +import logging +import re +import random +import string +from contextlib import contextmanager + +# Configure logging +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) + + +class WiFiManager: + def __init__(self, **kwargs): + """ + Initialize the WiFiManager with dynamic arguments. + kwargs: Dictionary of configuration arguments. + """ + self._command = "nmcli" + self.interface = kwargs.get("interface") + self.type = kwargs.get("type") + self.mode = kwargs.get("mode") + self.band = kwargs.get("band") + self.channel = kwargs.get("channel") + self.key_mgmt = kwargs.get("keymgmt") + self.group = kwargs.get("group") + self.peer = kwargs.get("peer") + self.ssid = kwargs.get("ssid") + self.ssid_pwd = kwargs.get("ssid_pwd") + self.conname = "qa-test-ap" + + def init_conn(self): + logging.info("Initializing connection") + if self.type == "wifi": + run_command( + "{} c add type {} ifname {} con-name {} " + "autoconnect no wifi.ssid {} " + "wifi.mode {} ipv4.method shared".format( + self._command, + self.type, + self.interface, + self.conname, + self.ssid, + self.mode, + ) + ) + self.set_band_channel() + if self.key_mgmt: + self.set_secured() + elif self.type == "wifi-p2p": + run_command( + "{} c add type {} ifname {} con-name {} " + "wifi-p2p.peer {}".format( + self._command, + self.type, + self.interface, + self.conname, + self.peer, + ) + ) + else: + raise ValueError("Unsupported type: {}".format(self.type)) + + def set_band_channel(self): + """ + Set band, channel and channel-width. + If channel and channel-width in 0, run command with setting band only. + """ + cmd = "{} c modify {} wifi.band {} ".format( + self._command, self.conname, self.band + ) + if self.channel: + cmd += "wifi.channel {} ".format(self.channel) + run_command(cmd) + + def set_secured(self): + run_command( + "{} c modify {} wifi-sec.key-mgmt {} wifi-sec.psk {} " + "wifi-sec.group {}".format( + self._command, + self.conname, + self.key_mgmt, + self.ssid_pwd, + self.group, + ) + ) + + def get_ip_addr(self): + """ + nmcli -g IP4.ADDRESS command will return the IPv4 address of the + interface with netmask. + e.g. 10.102.99.224/22 + """ + ip_addr = run_command( + "{} -g IP4.ADDRESS device show {}".format( + self._command, + self.interface, + ) + ) + ip_addr = ip_addr.split("/")[0] if ip_addr.find("/") != -1 else "" + return ip_addr + + def up_cmd(self): + return "{} c up {}".format(self._command, self.conname) + + def up_conn(self): + try: + run_command(self.up_cmd()) + logging.info("Initialized connection successful!") + except Exception: + raise SystemError("Bring up connection failed!") + for i in range(1, 6): + try: + ip_addr = self.get_ip_addr() + if ip_addr: + logging.info("IP address is {}".format(ip_addr)) + return True + except subprocess.CalledProcessError: + pass + time.sleep(5) + return False + + def del_cmd(self): + return "{} c delete {}".format(self._command, self.conname) + + def connect_dut_cmd(self, host_if): + connect_cmd = ( + "{} con add type wifi ifname {} con-name {} ssid {}".format( + self._command, host_if, self.conname, self.ssid + ) + ) + if self.key_mgmt: + connect_cmd += " wifi-sec.key-mgmt {}" " wifi-sec.psk {}".format( + self.key_mgmt, self.ssid_pwd + ) + if self.mode == "adhoc": + connect_cmd += " wifi.mode {}".format(self.mode) + return connect_cmd + + def __enter__(self): + self.init_conn() + if not self.up_conn(): + raise RuntimeError("Connection initialization failed!") + return self + + def __exit__(self, exc_type, exc_value, traceback): + logging.info("Exiting context and cleaning up connection") + cmd = self.del_cmd() + run_command(cmd) + + +def run_command(command): + logging.info("Run command: %s", command) + output = subprocess.check_output( + command, shell=True, text=True, stderr=subprocess.STDOUT + ) + return output + + +def sshpass_cmd_gen(ip, user, pwd, cmd): + return "sshpass -p {} ssh -o StrictHostKeyChecking=no {}@{} {}".format( + pwd, user, ip, cmd + ) + + +def ping_cmd(ip): + return "ping {} -c 4".format(ip) + + +@contextmanager +def connect_dut_from_host_via_wifi(host_net_info: dict, connect_info: dict): + ip = host_net_info["ip"] + user = host_net_info["user"] + pwd = host_net_info["pwd"] + ssid = connect_info["ssid"] + connect_cmd = connect_info["connect_cmd"] + del_host_conn = connect_info["delete_cmd"] + up_host_conn = connect_info["up_cmd"] + connected = False + + logging.info("Pinging target host first...") + try: + run_command(ping_cmd(ip)) + logging.info("Ping to target host %s successful.", ip) + except Exception as e: + raise SystemError("Unable to ping the HOST! Error: %s", str(e)) + try: + connected = create_conn_from_host(ip, user, pwd, connect_cmd) + if connected: + if bring_up_conn_from_host(ip, user, pwd, up_host_conn): + yield + except Exception as e: + raise SystemError(e) + finally: + if connected: + try: + run_command(sshpass_cmd_gen(ip, user, pwd, del_host_conn)) + logging.info("Deleted host connection successfully.") + except Exception as e: + raise SystemError( + "Failed to delete host connection: %s", str(e) + ) + else: + raise SystemError( + "Unable to connect to DUT AP SSID %s after 10 attempts.", ssid + ) + + +def create_conn_from_host(ip, user, pwd, connect_cmd): + logging.info("Attempting to create the connection configuration on HOST") + try: + run_command(sshpass_cmd_gen(ip, user, pwd, connect_cmd)) + logging.info("Create connection configuration successful!") + return True + except Exception as e: + logging.warning( + "Unable to create connection configuration on HOST. %s", str(e) + ) + + +def bring_up_conn_from_host(ip, user, pwd, up_host_conn): + for i in range(1, 4): + logging.info( + "Attempting to bring up the connection on HOST (%d/%d)...", i, 3 + ) + try: + run_command(sshpass_cmd_gen(ip, user, pwd, up_host_conn)) + logging.info("Bring up connection successful!") + return True + except Exception as e: + logging.warning( + "Unable to bring up connection on HOST. Attempt %d failed." + " Error: %s", + i, + str(e), + ) + time.sleep(10) + + +def ping_test(target_ip, host_net_info: dict): + ip = host_net_info["ip"] + user = host_net_info["user"] + pwd = host_net_info["pwd"] + try: + logging.info("Attempting to ping DUT...") + ping_result = run_command( + sshpass_cmd_gen(ip, user, pwd, ping_cmd(target_ip)) + ) + + packet_loss_match = re.search(r"(\d+)% packet loss", ping_result) + if packet_loss_match: + packet_loss = packet_loss_match.group(1) + logging.info("Packet loss: %s %%", packet_loss) + if packet_loss == "0": + logging.info("Ping DUT passed.") + return 0 + else: + logging.error( + "Ping DUT failed with %s %% packet loss!", packet_loss + ) + else: + logging.error("Could not parse packet loss from ping result.") + + except Exception as e: + logging.error("An error occurred during ping_test: %s", str(e)) + return 1 + + +def main(): + parser = argparse.ArgumentParser(description="WiFi test") + subparsers = parser.add_subparsers( + dest="type", + required=True, + help="Type of connection. " 'Currentlly support "wifi" and "wifi-p2p"', + ) + # Subparser for 'wifi' + wifi_parser = subparsers.add_parser("wifi", help="WiFi configuration") + wifi_parser.add_argument( + "--mode", + choices=["ap", "adhoc"], + required=True, + help="WiFi mode: ap or adhoc", + ) + wifi_parser.add_argument("--band", required=True, help="WiFi band to use") + wifi_parser.add_argument( + "--channel", required=True, type=int, help="WiFi channel to use" + ) + wifi_parser.add_argument( + "--keymgmt", required=False, help="Key management method" + ) + wifi_parser.add_argument( + "--group", required=False, help="Group key management method" + ) + wifi_parser.add_argument( + "--ssid", + default="".join( + [random.choice(string.ascii_letters) for _ in range(10)] + ), + required=False, + help="SSID for AP mode", + ) + wifi_parser.add_argument( + "--ssid-pwd", + required=False, + default="insecure", + help="Password for SSID", + ) + + # Subparser for 'wifi-p2p' + wifi_p2p_parser = subparsers.add_parser( + "wifi-p2p", help="WiFi P2P configuration" + ) + wifi_p2p_parser.add_argument( + "--peer", required=True, help="MAC address for P2P peer" + ) + parser.add_argument( + "--interface", required=True, help="WiFi interface to use" + ) + parser.add_argument( + "--host-ip", + required=True, + help="IP address of the Host device to connect to." + "The HOST is a device to access the DUT's AP.", + ) + parser.add_argument( + "--host-user", + required=True, + help="Username of the Host device for SSH connection", + ) + parser.add_argument( + "--host-pwd", + required=True, + help="Password of the Host device for SSH connection", + ) + parser.add_argument( + "--host-interface", + required=True, + help="The wifi interface name of the Host device", + ) + + args = parser.parse_args() + config = vars(args) + try: + with WiFiManager(**config) as manager: + host_net_info = { + "ip": args.host_ip, + "user": args.host_user, + "pwd": args.host_pwd, + } + connect_info = { + "ssid": args.ssid, + "connect_cmd": manager.connect_dut_cmd(args.host_interface), + "delete_cmd": manager.del_cmd(), + "up_cmd": manager.up_cmd(), + } + with connect_dut_from_host_via_wifi(host_net_info, connect_info): + ret = ping_test(manager.get_ip_addr(), host_net_info) + sys.exit(ret) + except SystemError as e: + logging.error(e) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu index 89f64fbb7f..c5d78f0f35 100644 --- a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/test-plan-ce-oem.pxu @@ -89,6 +89,7 @@ nested_part: before-suspend-ce-oem-spi-automated ce-oem-gadget-automated ce-oem-mir-automated + ce-oem-wifi-ap-automated ce-oem-power-automated-by-pdu certification_status_overrides: apply blocker to .* diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/README.md b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/README.md new file mode 100644 index 0000000000..89f62b136a --- /dev/null +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/README.md @@ -0,0 +1,53 @@ +# This is a file introducing WiFi AP mode test jobs. +To perform WiFi AP mode test, you will need following environment: +>Both DUT and HOST with Netwrok-Manager installed and wireless interface is managed by Network-Manager\ +And must have network connection between DUT and HOST via ethernet port, since we are intend to test wireless. + +## id: ce_oem_wifi_ap_mode + This resource job requires the checkbox environment variable `WIFI_AP_MODE`. + It defines which WiFi AP mode should be test includes different wireless interface, + band, key_mgmt, group and password.\ + Usage of parameter:\ + WIFI_AP_MODE={interface_name}:{band in a|bg}:{channel}:{key_mgmt}:{group in one of ciphers wep40|wep104|tkip|ccmp} +>WIFI_AP_MODE=wlan0:a:44:wpa-psk:ccmp wlan0:bg:5:none:none + +Above config defined two different AP mode that intend to test. +The following will break down first part of the config.\ +>wlan0:a:44:wpa-psk:ccmp:insecure +>>wlan0: The interface name to use\ + a: The band to use\ + 44: The channel to use\ + wpa-psk: The key_magmt to use\ + ccmp: The cipher group to use + +>waln0:bg:5:none:none +>>wlan0: The interface name to use\ + bg: The band to use\ + 5: The channel to use\ + none: Setup as open AP\ + none: No need cipher group for a open AP + +>Note: Netwrok Manager support only band A and BG. Please refer to follows for more detail. + https://networkmanager.dev/docs/api/latest/settings-802-11-wireless.html + https://networkmanager.dev/docs/api/latest/settings-802-11-wireless-security.html + +## id: ce-oem-wireless/ap_open_{band}_ch{channel}_{group}_{interface}_manual +A template job for open AP mode manual test. It will depend on resource job `ce_oem_wifi_ap_mode` to generate the related jobs.\ +In this job. It will setup the target AP mode on DUT, and tester need to connect to +the ssid `qa-test-ssid` and ping the DUT default IP address `10.42.0.1` on a HOST machine. + +## id: ce-oem-wireless/ap_wpa_{key_mgmt}_{band}_ch{channel}_{group}_{interface}_manual +A template job for WPA AP mode manual test. It will depend on resource job `ce_oem_wifi_ap_mode` to generate the related jobs.\ +In this job. It will setup the target AP mode on DUT, and tester need to connect to +the ssid `qa-test-ssid` with password `insecure`, and ping the DUT default IP address +`10.42.0.1` on a HOST machine. + +## id: ce-oem-wireless/ap_open_{band}_ch{channel}_{group}_{interface}_automated +A template job for open AP mode automated test. It will depend on resource job `ce_oem_wifi_ap_mode` to generate the related jobs.\ +This job requires the checkbox environment variables `WIFI_AP_HOST_DEVICE_IP` `WIFI_AP_HOST_DEVICE_USER` `WIFI_AP_HOST_DEVICE_PWD` to allow auto login to HOST machine +and perform connecting AP and ping DUT automaticlly by using `sshpass` command. + +## id: ce-oem-wireless/ap_wpa_{key_mgmt}_{band}_ch{channel}_{group}_{interface}_automated +A template job for WPA AP mode automated test. It will depend on resource job `ce_oem_wifi_ap_mode` to generate the related jobs.\ +This job requires the checkbox environment variables `WIFI_AP_HOST_DEVICE_IP` `WIFI_AP_HOST_DEVICE_USER` `WIFI_AP_HOST_DEVICE_PWD` to allow auto login to HOST machine +and perform connecting AP and ping DUT automaticlly by using `sshpass` command. diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/test-plan.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/test-plan.pxu new file mode 100644 index 0000000000..765f33da3a --- /dev/null +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/test-plan.pxu @@ -0,0 +1,30 @@ +id: ce-oem-wifi-ap-full +unit: test plan +_name: ce-oem WiFi AP mode tests +_description: Full WiFi AP mode tests for ce-oem +include: +nested_part: + ce-oem-wifi-ap-manual + ce-oem-wifi-ap-automated + +id: ce-oem-wifi-ap-manual +unit: test plan +_name: ce-oem manual WiFi AP mode tests +_description: Manual WiFi AP mode tests +mandatory_include: +bootstrap_include: +include: + +id: ce-oem-wifi-ap-automated +unit: test plan +_name: ce-oem automated WiFi AP mode tests +_description: automated WiFi AP mode tests for ce-oem +mandatory_include: +bootstrap_include: + com.canonical.certification::device + com.canonical.certification::wifi_interface_mode + com.canonical.certification::net_if_management + ce_oem_wifi_ap_mode +include: + ce-oem-wireless/ap_open_.*_automated + ce-oem-wireless/ap_wpa_.*_automated diff --git a/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/wifi-ap.pxu b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/wifi-ap.pxu new file mode 100644 index 0000000000..83961119a3 --- /dev/null +++ b/contrib/checkbox-ce-oem/checkbox-provider-ce-oem/units/wifi/wifi-ap.pxu @@ -0,0 +1,75 @@ +id: ce_oem_wifi_ap_mode +_summary: Generates a WiFi Access Point list for test +_description: + A WiFi Access Point mapping resource that relies on the user specifying in config varirable. + Usage of parameter: + WIFI_AP_MODE={interface_name}:{band in a|bg}:{channel}:{key_mgmt}:{group in one of ciphers wep40|wep104|tkip|ccmp} + e.g. WIFI_AP_MODE=wlP1p1s0f0:a:36:wpa-psk:ccmp wlP1p1s0f0:bg:5:none:none + For more detail please check + https://networkmanager.dev/docs/api/latest/settings-802-11-wireless.html + https://networkmanager.dev/docs/api/latest/settings-802-11-wireless-security.html +estimated_duration: 0.02 +plugin: resource +environ: WIFI_AP_MODE +command: + # We will generate a default set of WIFI_AP_MODE to test both a and bg + # band in open and wpa-psk with the first avaliable interface + # if WIFI_AP_MODE has not been set. + if [ -z "$WIFI_AP_MODE" ]; then + interface=$(iw dev|grep Interface|awk 'NR==1{print $2 }') + if [ -n "$interface" ]; then + WIFI_AP_MODE="$interface:a:36:none:none $interface:bg:5:none:none $interface:a:36:wpa-psk:ccmp $interface:bg:5:wpa-psk:ccmp" + fi + fi + awk '{ + split($0, record, " ") + for (i in record) { + split(record[i], data, ":") + printf "interface: %s\nband: %s\nchannel: %s\nkey_mgmt: %s\ngroup: %s\n\n", data[1], data[2], data[3], data[4], data[5] + } + }' <<< "$WIFI_AP_MODE" + +unit: template +template-resource: ce_oem_wifi_ap_mode +template-filter: ce_oem_wifi_ap_mode.key_mgmt == 'none' +template-unit: job +id: ce-oem-wireless/ap_open_{band}_ch{channel}_{group}_{interface}_automated +category_id: com.canonical.certification::wifi_ap +_summary: Create open 802.11{band} Wi-Fi AP on {interface} and check connection +_description: + Create open 802.11{band} Wi-Fi AP on {interface} in channel{channel} group {group} and check connection +plugin: shell +environ: WIFI_AP_HOST_DEVICE_IP WIFI_AP_HOST_DEVICE_USER WIFI_AP_HOST_DEVICE_PWD WIFI_AP_HOST_DEVICE_INTERFACE +imports: + from com.canonical.certification import net_if_management +requires: + net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'NetworkManager' +estimated_duration: 120.0 +flags: preserve-locale also-after-suspend +command: + wifi_test.py --interface {interface} --host-ip "$WIFI_AP_HOST_DEVICE_IP" \ + --host-user "$WIFI_AP_HOST_DEVICE_USER" --host-pwd "$WIFI_AP_HOST_DEVICE_PWD" \ + --host-interface "$WIFI_AP_HOST_DEVICE_INTERFACE" wifi --mode ap --band {band} --channel {channel} + +unit: template +template-resource: ce_oem_wifi_ap_mode +template-filter: ce_oem_wifi_ap_mode.key_mgmt != 'none' +template-unit: job +id: ce-oem-wireless/ap_wpa_{key_mgmt}_{band}_ch{channel}_{group}_{interface}_automated +category_id: com.canonical.certification::wifi_ap +_summary: Create wpa 802.11{band} Wi-Fi AP on {interface} and check connection +_description: + Create wpa 802.11{band} Wi-Fi AP on {interface} in channel{channel} group {group} and check connection +plugin: shell +environ: WIFI_AP_HOST_DEVICE_IP WIFI_AP_HOST_DEVICE_USER WIFI_AP_HOST_DEVICE_PWD WIFI_AP_HOST_DEVICE_INTERFACE +imports: + from com.canonical.certification import net_if_management +requires: + net_if_management.device == '{interface}' and net_if_management.master_mode_managed_by == 'NetworkManager' +estimated_duration: 120.0 +flags: preserve-locale also-after-suspend +command: + wifi_test.py --interface {interface} --host-ip "$WIFI_AP_HOST_DEVICE_IP" \ + --host-user "$WIFI_AP_HOST_DEVICE_USER" --host-pwd "$WIFI_AP_HOST_DEVICE_PWD" \ + --host-interface "$WIFI_AP_HOST_DEVICE_INTERFACE" wifi --mode ap --band {band} \ + --channel {channel} --keymgmt {key_mgmt} --group {group}