Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 29780fbc authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Cert: Free resources occupying required ports during setup"

parents 639860bd ebd72a96
Loading
Loading
Loading
Loading
+28 −7
Original line number Diff line number Diff line
@@ -23,7 +23,10 @@ import subprocess
from acts import asserts
from acts.context import get_current_context
from acts.base_test import BaseTestClass
from cert.os_utils import get_gd_root, is_subprocess_alive

from cert.os_utils import get_gd_root
from cert.os_utils import is_subprocess_alive
from cert.os_utils import make_ports_available
from facade import rootservice_pb2 as facade_rootservice


@@ -40,18 +43,35 @@ class GdBaseTestClass(BaseTestClass):
        self.rootcanal_running = False
        if 'rootcanal' in self.controller_configs:
            self.rootcanal_running = True
            # Get root canal binary
            rootcanal = os.path.join(get_gd_root(), "root-canal")
            asserts.assert_true(
                os.path.isfile(rootcanal),
                "Root canal does not exist at %s" % rootcanal)

            # Get root canal log
            rootcanal_logpath = os.path.join(self.log_path_base,
                                             'rootcanal_logs.txt')
            self.rootcanal_logs = open(rootcanal_logpath, 'w')

            # Make sure ports are available
            rootcanal_config = self.controller_configs['rootcanal']
            rootcanal_hci_port = str(rootcanal_config.get("hci_port", "6402"))
            rootcanal = os.path.join(get_gd_root(), "root-canal")
            rootcanal_test_port = int(rootcanal_config.get("test_port", "6401"))
            rootcanal_hci_port = int(rootcanal_config.get("hci_port", "6402"))
            rootcanal_link_layer_port = int(
                rootcanal_config.get("link_layer_port", "6403"))
            asserts.assert_true(
                make_ports_available((rootcanal_test_port, rootcanal_hci_port,
                                      rootcanal_link_layer_port)),
                "Failed to make root canal ports available")

            # Start root canal process
            self.rootcanal_process = subprocess.Popen(
                [
                    rootcanal,
                    str(rootcanal_config.get("test_port", "6401")),
                    rootcanal_hci_port,
                    str(rootcanal_config.get("link_layer_port", "6403"))
                    str(rootcanal_test_port),
                    str(rootcanal_hci_port),
                    str(rootcanal_link_layer_port)
                ],
                cwd=get_gd_root(),
                env=os.environ.copy(),
@@ -63,9 +83,10 @@ class GdBaseTestClass(BaseTestClass):
            asserts.assert_true(
                is_subprocess_alive(self.rootcanal_process),
                msg="root-canal stopped immediately after running")

            # Modify the device config to include the correct root-canal port
            for gd_device_config in self.controller_configs.get("GdDevice"):
                gd_device_config["rootcanal_port"] = rootcanal_hci_port
                gd_device_config["rootcanal_port"] = str(rootcanal_hci_port)

        # Parse and construct GD device objects
        self.register_controller(
+44 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ from google.protobuf import empty_pb2 as empty_proto

from cert.os_utils import get_gd_root
from cert.os_utils import is_subprocess_alive
from cert.os_utils import make_ports_available
from facade import rootservice_pb2_grpc as facade_rootservice_pb2_grpc
from hal import facade_pb2_grpc as hal_facade_pb2_grpc
from hci.facade import facade_pb2_grpc as hci_facade_pb2_grpc
@@ -174,6 +175,11 @@ class GdDeviceBase(ABC):
        - Should be executed after children classes' setup() methods
        :return:
        """
        # Ensure signal port is available
        # signal port is the only port that always listen on the host machine
        asserts.assert_true(
            make_ports_available([self.signal_port]),
            "[%s] Failed to make signal port available" % self.label)
        # Start backing process
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as signal_socket:
            # Setup signaling socket
@@ -289,6 +295,16 @@ class GdHostOnlyDevice(GdDeviceBase):
            self.log_path_base, "%s_%s_backing_coverage.profraw" %
            (self.type_identifier, self.label))

    def setup(self):
        # Ensure ports are available
        # Only check on host only test, for Android devices, these ports will
        # be opened on Android device and host machine ports will be occupied
        # by sshd or adb forwarding
        asserts.assert_true(
            make_ports_available((self.grpc_port, self.grpc_root_server_port)),
            "[%s] Failed to make backing process ports available" % self.label)
        super().setup()


class GdAndroidDevice(GdDeviceBase):
    """Real Android device where the backing process is running on it
@@ -313,10 +329,16 @@ class GdAndroidDevice(GdDeviceBase):
            msg="device %s cannot run as root after enabling verity" %
            self.serial_number)
        self.adb.shell("date " + time.strftime("%m%d%H%M%Y.%S"))
        # Try freeing ports and ignore results
        self.adb.remove_tcp_forward(self.grpc_port)
        self.adb.remove_tcp_forward(self.grpc_root_server_port)
        self.adb.reverse("--remove tcp:%d" % self.signal_port)
        # Set up port forwarding or reverse or die
        self.tcp_forward_or_die(self.grpc_port, self.grpc_port)
        self.tcp_forward_or_die(self.grpc_root_server_port,
                                self.grpc_root_server_port)
        self.tcp_reverse_or_die(self.signal_port, self.signal_port)
        # Puh test binaries
        self.push_or_die(
            os.path.join(get_gd_root(), "target",
                         "bluetooth_stack_with_facade"), "system/bin")
@@ -374,11 +396,12 @@ class GdAndroidDevice(GdDeviceBase):
                (src_file_path, dst_file_path, e),
                extras=e)

    def tcp_forward_or_die(self, host_port, device_port):
    def tcp_forward_or_die(self, host_port, device_port, num_retry=1):
        """
        Forward a TCP port from host to device or fail
        :param host_port: host port, int, 0 for adb to assign one
        :param device_port: device port, int
        :param num_retry: number of times to reboot and retry this before dying
        :return: host port int
        """
        error_or_port = self.adb.tcp_forward(host_port, device_port)
@@ -386,16 +409,26 @@ class GdAndroidDevice(GdDeviceBase):
            logging.debug("host port %d was already forwarded" % host_port)
            return host_port
        if not isinstance(error_or_port, int):
            if num_retry > 0:
                # If requested, reboot an retry
                num_retry -= 1
                logging.warning("[%s] Failed to TCP forward host port %d to "
                                "device port %d, num_retries left is %d" %
                                (self.label, host_port, device_port, num_retry))
                self.reboot()
                return self.tcp_forward_or_die(
                    host_port, device_port, num_retry=num_retry)
            asserts.fail(
                'Unable to forward host port %d to device port %d, error %s' %
                (host_port, device_port, error_or_port))
        return error_or_port

    def tcp_reverse_or_die(self, device_port, host_port):
    def tcp_reverse_or_die(self, device_port, host_port, num_retry=1):
        """
        Forward a TCP port from device to host or fail
        :param device_port: device port, int, 0 for adb to assign one
        :param host_port: host port, int
        :param num_retry: number of times to reboot and retry this before dying
        :return: device port int
        """
        error_or_port = self.adb.reverse(
@@ -406,6 +439,15 @@ class GdAndroidDevice(GdDeviceBase):
        try:
            error_or_port = int(error_or_port)
        except ValueError:
            if num_retry > 0:
                # If requested, reboot an retry
                num_retry -= 1
                logging.warning("[%s] Failed to TCP reverse device port %d to "
                                "host port %d, num_retries left is %d" %
                                (self.label, device_port, host_port, num_retry))
                self.reboot()
                return self.tcp_reverse_or_die(
                    device_port, host_port, num_retry=num_retry)
            asserts.fail(
                'Unable to reverse device port %d to host port %d, error %s' %
                (device_port, host_port, error_or_port))
+41 −0
Original line number Diff line number Diff line
@@ -14,8 +14,11 @@
#   See the License for the specific language governing permissions and
#   limitations under the License.

import logging
from pathlib import Path
import psutil
import subprocess
from typing import Container


def is_subprocess_alive(process, timeout_seconds=1):
@@ -41,3 +44,41 @@ def get_gd_root():
    :return: root directory string of gd test library
    """
    return str(Path(__file__).absolute().parents[1])


def make_ports_available(ports: Container[int], timeout_seconds=10):
    """Make sure a list of ports are available
    kill occupying process if possible
    :param ports: list of target ports
    :param timeout_seconds: number of seconds to wait when killing processes
    :return: True on success, False on failure
    """
    if not ports:
        logging.warning("Empty ports is given to make_ports_available()")
        return True
    # Get connections whose state are in LISTEN only
    # Connections in other states won't affect binding as SO_REUSEADDR is used
    listening_conns_for_port = filter(
        lambda conn: (conn and conn.status == psutil.CONN_LISTEN and conn.laddr and conn.laddr.port in ports),
        psutil.net_connections())
    success = True
    for conn in listening_conns_for_port:
        logging.warning(
            "Freeing port %d used by %s" % (conn.laddr.port, str(conn)))
        if not conn.pid:
            logging.error(
                "Failed to kill process occupying port %d due to lack of pid" %
                conn.laddr.port)
            success = False
            continue
        logging.warning("Killing pid %d that is using port port %d" %
                        (conn.pid, conn.laddr.port))
        process = psutil.Process(conn.pid)
        process.kill()
        try:
            process.wait(timeout=timeout_seconds)
        except psutil.TimeoutExpired:
            logging.error("SIGKILL timeout after %d seconds for pid %d" %
                          (timeout_seconds, conn.pid))
            continue
    return success
+1 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import sys

install_requires = [
    'grpcio',
    'psutil',
]

host_executables = [