diff options
author | Jack He <siyuanh@google.com> | 2021-11-23 13:00:46 -0800 |
---|---|---|
committer | Jack He <siyuanh@google.com> | 2022-01-07 13:25:09 -0800 |
commit | 9b6a25306351ed6bcfad8c7651d6e2359ab44d73 (patch) | |
tree | c5734f898e8ed29c7c298f13554a63a1ae45aacf | |
parent | 8df0a19785fc4094435e0447570802da88345572 (diff) | |
download | connectivity-9b6a25306351ed6bcfad8c7651d6e2359ab44d73.tar.gz |
SL4A: Add options to override client, server, and forwarded port
* When setting up SL4A tests over ssh-forwarding, we need to control
which port is used on the host so that we can forward it to a remote
machine
Bug: 207525224
Test: gd/cert/run LeAdvancedScanningTest
Change-Id: I684cf3f50eb0b68274423743606b796c8875eb91
Merged-In: I684cf3f50eb0b68274423743606b796c8875eb91
(cherry picked from commit f0378706d9e5faada04a3ed7ea67237c4a99ab26)
3 files changed, 157 insertions, 93 deletions
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py index e5f568f5e..3a35bf3bd 100755 --- a/acts/framework/acts/controllers/android_device.py +++ b/acts/framework/acts/controllers/android_device.py @@ -47,6 +47,10 @@ MOBLY_CONTROLLER_CONFIG_NAME = "AndroidDevice" ACTS_CONTROLLER_REFERENCE_NAME = "android_devices" ANDROID_DEVICE_PICK_ALL_TOKEN = "*" +# Key name for SL4A extra params in config file +ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY = "sl4a_client_port" +ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY = "sl4a_forwarded_port" +ANDROID_DEVICE_SL4A_SERVER_PORT_KEY = "sl4a_server_port" # Key name for adb logcat extra params in config file. ANDROID_DEVICE_ADB_LOGCAT_PARAM_KEY = "adb_logcat_param" ANDROID_DEVICE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!" @@ -232,12 +236,41 @@ def get_instances_with_configs(configs): raise errors.AndroidDeviceConfigError( "Required value 'serial' is missing in AndroidDevice config %s." % c) + client_port = 0 + if ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY in c: + try: + client_port = int(c.pop(ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY)) + except ValueError: + raise errors.AndroidDeviceConfigError( + "'%s' is not a valid number for config %s" % + (ANDROID_DEVICE_SL4A_CLIENT_PORT_KEY, c)) + server_port = None + if ANDROID_DEVICE_SL4A_SERVER_PORT_KEY in c: + try: + server_port = int(c.pop(ANDROID_DEVICE_SL4A_SERVER_PORT_KEY)) + except ValueError: + raise errors.AndroidDeviceConfigError( + "'%s' is not a valid number for config %s" % + (ANDROID_DEVICE_SL4A_SERVER_PORT_KEY, c)) + forwarded_port = 0 + if ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY in c: + try: + forwarded_port = int( + c.pop(ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY)) + except ValueError: + raise errors.AndroidDeviceConfigError( + "'%s' is not a valid number for config %s" % + (ANDROID_DEVICE_SL4A_FORWARDED_PORT_KEY, c)) ssh_config = c.pop('ssh_config', None) ssh_connection = None if ssh_config is not None: ssh_settings = settings.from_config(ssh_config) ssh_connection = connection.SshConnection(ssh_settings) - ad = AndroidDevice(serial, ssh_connection=ssh_connection) + ad = AndroidDevice(serial, + ssh_connection=ssh_connection, + client_port=client_port, + forwarded_port=forwarded_port, + server_port=server_port) ad.load_config(c) results.append(ad) return results @@ -357,14 +390,27 @@ class AndroidDevice: adb: An AdbProxy object used for interacting with the device via adb. fastboot: A FastbootProxy object used for interacting with the device via fastboot. + client_port: Preferred client port number on the PC host side for SL4A + forwarded_port: Preferred server port number forwarded from Android + to the host PC via adb for SL4A connections + server_port: Preferred server port used by SL4A on Android device + """ - def __init__(self, serial='', ssh_connection=None): + def __init__(self, + serial='', + ssh_connection=None, + client_port=0, + forwarded_port=0, + server_port=None): self.serial = serial # logging.log_path only exists when this is used in an ACTS test run. log_path_base = getattr(logging, 'log_path', '/tmp/logs') self.log_dir = 'AndroidDevice%s' % serial self.log_path = os.path.join(log_path_base, self.log_dir) + self.client_port = client_port + self.forwarded_port = forwarded_port + self.server_port = server_port self.log = tracelogger.TraceLogger( AndroidDeviceLoggerAdapter(logging.getLogger(), {'serial': serial})) @@ -374,8 +420,8 @@ class AndroidDevice: self.register_service(services.Sl4aService(self)) self.adb_logcat_process = None self.adb = adb.AdbProxy(serial, ssh_connection=ssh_connection) - self.fastboot = fastboot.FastbootProxy( - serial, ssh_connection=ssh_connection) + self.fastboot = fastboot.FastbootProxy(serial, + ssh_connection=ssh_connection) if not self.is_bootloader: self.root_adb() self._ssh_connection = ssh_connection @@ -425,8 +471,8 @@ class AndroidDevice: Stop adb logcat and terminate sl4a sessions if exist. """ - event_bus.post( - android_events.AndroidStopServicesEvent(self), ignore_errors=True) + event_bus.post(android_events.AndroidStopServicesEvent(self), + ignore_errors=True) def is_connected(self): out = self.adb.devices() @@ -651,7 +697,13 @@ class AndroidDevice: >>> ad = AndroidDevice() >>> droid, ed = ad.get_droid() """ - session = self._sl4a_manager.create_session() + self.log.debug( + "Creating RPC client_port={}, forwarded_port={}, server_port={}". + format(self.client_port, self.forwarded_port, self.server_port)) + session = self._sl4a_manager.create_session( + client_port=self.client_port, + forwarded_port=self.forwarded_port, + server_port=self.server_port) droid = session.rpc_client if handle_event: ed = session.get_event_dispatcher() @@ -671,9 +723,8 @@ class AndroidDevice: """ for cmd in ("ps -A", "ps"): try: - out = self.adb.shell( - '%s | grep "S %s"' % (cmd, package_name), - ignore_status=True) + out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name), + ignore_status=True) if package_name not in out: continue try: @@ -765,10 +816,10 @@ class AndroidDevice: return adb_excerpt_path def search_logcat(self, - matching_string, - begin_time=None, - end_time=None, - logcat_path=None): + matching_string, + begin_time=None, + end_time=None, + logcat_path=None): """Search logcat message with given string. Args: @@ -798,13 +849,12 @@ class AndroidDevice: """ if not logcat_path: logcat_path = os.path.join(self.device_log_path, - 'adblog_%s_debug.txt' % self.serial) + 'adblog_%s_debug.txt' % self.serial) if not os.path.exists(logcat_path): self.log.warning("Logcat file %s does not exist." % logcat_path) return - output = job.run( - "grep '%s' %s" % (matching_string, logcat_path), - ignore_status=True) + output = job.run("grep '%s' %s" % (matching_string, logcat_path), + ignore_status=True) if not output.stdout or output.exit_status != 0: return [] if begin_time: @@ -812,13 +862,13 @@ class AndroidDevice: log_begin_time = acts_logger.epoch_to_log_line_timestamp( begin_time) begin_time = datetime.strptime(log_begin_time, - "%Y-%m-%d %H:%M:%S.%f") + "%Y-%m-%d %H:%M:%S.%f") if end_time: if not isinstance(end_time, datetime): log_end_time = acts_logger.epoch_to_log_line_timestamp( end_time) end_time = datetime.strptime(log_end_time, - "%Y-%m-%d %H:%M:%S.%f") + "%Y-%m-%d %H:%M:%S.%f") result = [] logs = re.findall(r'(\S+\s\S+)(.*)', output.stdout) for log in logs: @@ -890,8 +940,8 @@ class AndroidDevice: Returns: Linux UID for the apk. """ - output = self.adb.shell( - "dumpsys package %s | grep userId=" % apk_name, ignore_status=True) + output = self.adb.shell("dumpsys package %s | grep userId=" % apk_name, + ignore_status=True) result = re.search(r"userId=(\d+)", output) if result: return result.group(1) @@ -934,9 +984,8 @@ class AndroidDevice: """ for cmd in ("ps -A", "ps"): try: - out = self.adb.shell( - '%s | grep "S %s"' % (cmd, package_name), - ignore_status=True) + out = self.adb.shell('%s | grep "S %s"' % (cmd, package_name), + ignore_status=True) if package_name in out: self.log.info("apk %s is running", package_name) return True @@ -961,8 +1010,8 @@ class AndroidDevice: True if package is installed. False otherwise. """ try: - self.adb.shell( - 'am force-stop %s' % package_name, ignore_status=True) + self.adb.shell('am force-stop %s' % package_name, + ignore_status=True) except Exception as e: self.log.warn("Fail to stop package %s: %s", package_name, e) @@ -1010,8 +1059,8 @@ class AndroidDevice: br_out_path = out.split(':')[1].strip().split()[0] self.adb.pull("%s %s" % (br_out_path, full_out_path)) else: - self.adb.bugreport( - " > {}".format(full_out_path), timeout=BUG_REPORT_TIMEOUT) + self.adb.bugreport(" > {}".format(full_out_path), + timeout=BUG_REPORT_TIMEOUT) self.log.info("Bugreport for %s taken at %s.", test_name, full_out_path) self.adb.wait_for_device(timeout=WAIT_FOR_DEVICE_TIMEOUT) @@ -1073,10 +1122,10 @@ class AndroidDevice: if not host_path: host_path = self.log_path for device_path in device_paths: - self.log.info( - 'Pull from device: %s -> %s' % (device_path, host_path)) - self.adb.pull( - "%s %s" % (device_path, host_path), timeout=PULL_TIMEOUT) + self.log.info('Pull from device: %s -> %s' % + (device_path, host_path)) + self.adb.pull("%s %s" % (device_path, host_path), + timeout=PULL_TIMEOUT) def check_crash_report(self, test_name=None, @@ -1091,10 +1140,9 @@ class AndroidDevice: except Exception as e: self.log.debug("received exception %s", e) continue - crashes = self.get_file_names( - crash_path, - skip_files=CRASH_REPORT_SKIPS, - begin_time=begin_time) + crashes = self.get_file_names(crash_path, + skip_files=CRASH_REPORT_SKIPS, + begin_time=begin_time) if crash_path == "/data/tombstones/" and crashes: tombstones = crashes[:] for tombstone in tombstones: @@ -1117,18 +1165,18 @@ class AndroidDevice: # Sleep 10 seconds for the buffered log to be written in qxdm log file time.sleep(10) log_path = getattr(self, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH) - qxdm_logs = self.get_file_names( - log_path, begin_time=begin_time, match_string="*.qmdl") + qxdm_logs = self.get_file_names(log_path, + begin_time=begin_time, + match_string="*.qmdl") if qxdm_logs: qxdm_log_path = os.path.join(self.device_log_path, "QXDM_%s" % self.serial) os.makedirs(qxdm_log_path, exist_ok=True) self.log.info("Pull QXDM Log %s to %s", qxdm_logs, qxdm_log_path) self.pull_files(qxdm_logs, qxdm_log_path) - self.adb.pull( - "/firmware/image/qdsp6m.qdb %s" % qxdm_log_path, - timeout=PULL_TIMEOUT, - ignore_status=True) + self.adb.pull("/firmware/image/qdsp6m.qdb %s" % qxdm_log_path, + timeout=PULL_TIMEOUT, + ignore_status=True) else: self.log.error("Didn't find QXDM logs in %s." % log_path) if "Verizon" in self.adb.getprop("gsm.sim.operator.alpha"): @@ -1147,8 +1195,9 @@ class AndroidDevice: # Sleep 10 seconds for the buffered log to be written in sdm log file time.sleep(10) log_path = getattr(self, "sdm_log_path", DEFAULT_SDM_LOG_PATH) - sdm_logs = self.get_file_names( - log_path, begin_time=begin_time, match_string="*.sdm*") + sdm_logs = self.get_file_names(log_path, + begin_time=begin_time, + match_string="*.sdm*") if sdm_logs: sdm_log_path = os.path.join(self.device_log_path, "SDM_%s" % self.serial) @@ -1234,8 +1283,8 @@ class AndroidDevice: status: true if iperf client start successfully. results: results have data flow information """ - out = self.adb.shell( - "iperf3 -c {} {}".format(server_host, extra_args), timeout=timeout) + out = self.adb.shell("iperf3 -c {} {}".format(server_host, extra_args), + timeout=timeout) clean_out = out.split('\n') if "error" in clean_out[0].lower(): return False, clean_out @@ -1286,7 +1335,9 @@ class AndroidDevice: 'Device %s booting process timed out.' % self.serial, serial=self.serial) - def reboot(self, stop_at_lock_screen=False, timeout=180, + def reboot(self, + stop_at_lock_screen=False, + timeout=180, wait_after_reboot_complete=1): """Reboots the device. @@ -1323,8 +1374,8 @@ class AndroidDevice: # want the device to be missing to prove the device has kicked # off the reboot. break - self.wait_for_boot_completion( - timeout=(timeout - time.time() + timeout_start)) + self.wait_for_boot_completion(timeout=(timeout - time.time() + + timeout_start)) self.log.debug('Wait for a while after boot completion.') time.sleep(wait_after_reboot_complete) @@ -1359,8 +1410,8 @@ class AndroidDevice: break except adb.AdbError as e: if timer + 1 == timeout: - self.log.warning( - 'Unable to find IP address for %s.' % interface) + self.log.warning('Unable to find IP address for %s.' % + interface) return None else: time.sleep(1) @@ -1426,7 +1477,7 @@ class AndroidDevice: for cmd in dumpsys_cmd: output = self.adb.shell(cmd, ignore_status=True) if not output or "not found" in output or "Can't find" in output or ( - "mFocusedApp=null" in output): + "mFocusedApp=null" in output): result = '' else: result = output.split(' ')[-2] @@ -1565,11 +1616,11 @@ class AndroidDevice: return if not self.is_user_setup_complete() or self.is_setupwizard_on(): # b/116709539 need this to prevent reboot after skip setup wizard - self.adb.shell( - "am start -a com.android.setupwizard.EXIT", ignore_status=True) - self.adb.shell( - "pm disable %s" % self.get_setupwizard_package_name(), - ignore_status=True) + self.adb.shell("am start -a com.android.setupwizard.EXIT", + ignore_status=True) + self.adb.shell("pm disable %s" % + self.get_setupwizard_package_name(), + ignore_status=True) # Wait up to 5 seconds for user_setup_complete to be updated end_time = time.time() + 5 while time.time() < end_time: @@ -1619,8 +1670,8 @@ class AndroidDevice: try: self.ensure_verity_disabled() self.adb.remount() - out = self.adb.push( - '%s %s' % (src_file_path, dst_file_path), timeout=push_timeout) + out = self.adb.push('%s %s' % (src_file_path, dst_file_path), + timeout=push_timeout) if 'error' in out: self.log.error('Unable to push system file %s to %s due to %s', src_file_path, dst_file_path, out) diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py index 8d76f1fa4..181048dac 100644 --- a/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py +++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_manager.py @@ -109,12 +109,12 @@ class Sl4aManager(object): self._listen_for_port_lock = threading.Lock() self._sl4a_ports = set() self.adb = adb - self.log = logger.create_logger( - lambda msg: '[SL4A Manager|%s] %s' % (adb.serial, msg)) + self.log = logger.create_logger(lambda msg: '[SL4A Manager|%s] %s' % ( + adb.serial, msg)) self.sessions = {} self._started = False - self.error_reporter = error_reporter.ErrorReporter( - 'SL4A %s' % adb.serial) + self.error_reporter = error_reporter.ErrorReporter('SL4A %s' % + adb.serial) @property def sl4a_ports_in_use(self): @@ -161,8 +161,8 @@ class Sl4aManager(object): raise rpc_client.Sl4aConnectionError( 'Unable to find a valid open port for a new server connection. ' - 'Expected port: %s. Open ports: %s' % (device_port, - self._sl4a_ports)) + 'Expected port: %s. Open ports: %s' % + (device_port, self._sl4a_ports)) def _get_all_ports_command(self): """Returns the list of all ports from the command to get ports.""" @@ -203,9 +203,8 @@ class Sl4aManager(object): def is_sl4a_installed(self): """Returns True if SL4A is installed on the AndroidDevice.""" return bool( - self.adb.shell( - 'pm path com.googlecode\.android_scripting', - ignore_status=True)) + self.adb.shell('pm path com.googlecode\.android_scripting', + ignore_status=True)) def start_sl4a_service(self): """Starts the SL4A Service on the device. @@ -219,7 +218,8 @@ class Sl4aManager(object): raise rpc_client.Sl4aNotInstalledError( 'SL4A is not installed on device %s' % self.adb.serial) if self.adb.shell( - '(ps | grep "S com.googlecode.android_scripting") || true'): + '(ps | grep "S com.googlecode.android_scripting") || true' + ): # Close all SL4A servers not opened by this manager. # TODO(markdr): revert back to closing all ports after # b/76147680 is resolved. @@ -244,6 +244,7 @@ class Sl4aManager(object): def create_session(self, max_connections=None, client_port=0, + forwarded_port=0, server_port=None): """Creates an SL4A server with the given ports if possible. @@ -252,7 +253,9 @@ class Sl4aManager(object): be randomized. Args: - client_port: The port on the host machine + client_port: The client port on the host machine + forwarded_port: The server port on the host machine forwarded + by adb from the Android device server_port: The port on the Android device. max_connections: The max number of client connections for the session. @@ -268,14 +271,17 @@ class Sl4aManager(object): # Otherwise, open a new server on a random port. else: server_port = 0 + self.log.debug( + "Creating SL4A session client_port={}, forwarded_port={}, server_port={}" + .format(client_port, forwarded_port, server_port)) self.start_sl4a_service() - session = sl4a_session.Sl4aSession( - self.adb, - client_port, - server_port, - self.obtain_sl4a_server, - self.diagnose_failure, - max_connections=max_connections) + session = sl4a_session.Sl4aSession(self.adb, + client_port, + server_port, + self.obtain_sl4a_server, + self.diagnose_failure, + forwarded_port, + max_connections=max_connections) self.sessions[session.uid] = session return session diff --git a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py index c0a4e1dae..04fd78716 100644 --- a/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py +++ b/acts/framework/acts/controllers/sl4a_lib/sl4a_session.py @@ -55,6 +55,7 @@ class Sl4aSession(object): device_port, get_server_port_func, on_error_callback, + forwarded_port=0, max_connections=None): """Creates an SL4A Session. @@ -67,6 +68,8 @@ class Sl4aSession(object): server for its first connection. device_port: The SL4A server port to be used as a hint for which SL4A server to connect to. + forwarded_port: The server port on host machine forwarded by adb + from Android device to accept SL4A connection """ self._event_dispatcher = None self._terminate_lock = threading.Lock() @@ -79,24 +82,24 @@ class Sl4aSession(object): self.log = logger.create_logger(_log_formatter) + self.forwarded_port = forwarded_port self.server_port = device_port self.uid = UNKNOWN_UID self.obtain_server_port = get_server_port_func self._on_error_callback = on_error_callback connection_creator = self._rpc_connection_creator(host_port) - self.rpc_client = rpc_client.RpcClient( - self.uid, - self.adb.serial, - self.diagnose_failure, - connection_creator, - max_connections=max_connections) + self.rpc_client = rpc_client.RpcClient(self.uid, + self.adb.serial, + self.diagnose_failure, + connection_creator, + max_connections=max_connections) def _rpc_connection_creator(self, host_port): def create_client(uid): - return self._create_rpc_connection( - ports=sl4a_ports.Sl4aPorts(host_port, 0, self.server_port), - uid=uid) + return self._create_rpc_connection(ports=sl4a_ports.Sl4aPorts( + host_port, self.forwarded_port, self.server_port), + uid=uid) return create_client @@ -156,10 +159,14 @@ class Sl4aSession(object): ports.server_port = self.obtain_server_port(ports.server_port) self.server_port = ports.server_port # Forward the device port to the host. - ports.forwarded_port = self._create_forwarded_port(ports.server_port) + ports.forwarded_port = self._create_forwarded_port( + ports.server_port, hinted_port=ports.forwarded_port) client_socket, fd = self._create_client_side_connection(ports) - client = rpc_connection.RpcConnection( - self.adb, ports, client_socket, fd, uid=uid) + client = rpc_connection.RpcConnection(self.adb, + ports, + client_socket, + fd, + uid=uid) client.open() if uid == UNKNOWN_UID: self.uid = client.uid @@ -195,9 +202,9 @@ class Sl4aSession(object): except OSError as e: # If the port is in use, log and ask for any open port. if e.errno == errno.EADDRINUSE: - self.log.warning( - 'Port %s is already in use on the host. ' - 'Generating a random port.' % ports.client_port) + self.log.warning('Port %s is already in use on the host. ' + 'Generating a random port.' % + ports.client_port) ports.client_port = 0 return self._create_client_side_connection(ports) raise |