xref: /aosp_15_r20/external/autotest/server/hosts/chameleon_host.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li#
5*9c5db199SXin Li
6*9c5db199SXin Li"""This file provides core logic for connecting a Chameleon Daemon."""
7*9c5db199SXin Li
8*9c5db199SXin Liimport logging
9*9c5db199SXin Li
10*9c5db199SXin Lifrom autotest_lib.client.bin import utils
11*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
12*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
13*9c5db199SXin Lifrom autotest_lib.client.cros.chameleon import chameleon
14*9c5db199SXin Lifrom autotest_lib.server.cros import dnsname_mangler
15*9c5db199SXin Lifrom autotest_lib.server.cros.dynamic_suite import frontend_wrappers
16*9c5db199SXin Lifrom autotest_lib.server.hosts import ssh_host
17*9c5db199SXin Li
18*9c5db199SXin Li# Names of the host attributes in the database that represent the values for
19*9c5db199SXin Li# the chameleon_host and chameleon_port for a servo connected to the DUT.
20*9c5db199SXin LiCHAMELEON_HOST_ATTR = 'chameleon_host'
21*9c5db199SXin LiCHAMELEON_PORT_ATTR = 'chameleon_port'
22*9c5db199SXin Li
23*9c5db199SXin Li_CONFIG = global_config.global_config
24*9c5db199SXin LiENABLE_SSH_TUNNEL_FOR_CHAMELEON = _CONFIG.get_config_value(
25*9c5db199SXin Li        'CROS', 'enable_ssh_tunnel_for_chameleon', type=bool, default=False)
26*9c5db199SXin Li
27*9c5db199SXin Liclass ChameleonHostError(Exception):
28*9c5db199SXin Li    """Error in ChameleonHost."""
29*9c5db199SXin Li    pass
30*9c5db199SXin Li
31*9c5db199SXin Li
32*9c5db199SXin Liclass ChameleonHost(ssh_host.SSHHost):
33*9c5db199SXin Li    """Host class for a host that controls a Chameleon."""
34*9c5db199SXin Li
35*9c5db199SXin Li    # Chameleond process name.
36*9c5db199SXin Li    CHAMELEOND_PROCESS = 'chameleond'
37*9c5db199SXin Li
38*9c5db199SXin Li
39*9c5db199SXin Li    # TODO(waihong): Add verify and repair logic which are required while
40*9c5db199SXin Li    # deploying to Cros Lab.
41*9c5db199SXin Li
42*9c5db199SXin Li
43*9c5db199SXin Li    def _initialize(self, chameleon_host='localhost', chameleon_port=9992,
44*9c5db199SXin Li                    *args, **dargs):
45*9c5db199SXin Li        """Initialize a ChameleonHost instance.
46*9c5db199SXin Li
47*9c5db199SXin Li        A ChameleonHost instance represents a host that controls a Chameleon.
48*9c5db199SXin Li
49*9c5db199SXin Li        @param chameleon_host: Name of the host where the chameleond process
50*9c5db199SXin Li                               is running.
51*9c5db199SXin Li                               If this is passed in by IP address, it will be
52*9c5db199SXin Li                               treated as not in lab.
53*9c5db199SXin Li        @param chameleon_port: Port the chameleond process is listening on.
54*9c5db199SXin Li
55*9c5db199SXin Li        """
56*9c5db199SXin Li        super(ChameleonHost, self)._initialize(hostname=chameleon_host,
57*9c5db199SXin Li                                               *args, **dargs)
58*9c5db199SXin Li
59*9c5db199SXin Li        self._is_in_lab = None
60*9c5db199SXin Li        self._check_if_is_in_lab()
61*9c5db199SXin Li
62*9c5db199SXin Li        self._chameleon_port = chameleon_port
63*9c5db199SXin Li        self._local_port = None
64*9c5db199SXin Li        self._tunneling_process = None
65*9c5db199SXin Li
66*9c5db199SXin Li        try:
67*9c5db199SXin Li            if self._is_in_lab and not ENABLE_SSH_TUNNEL_FOR_CHAMELEON:
68*9c5db199SXin Li                self._chameleon_connection = chameleon.ChameleonConnection(
69*9c5db199SXin Li                        self.hostname, chameleon_port)
70*9c5db199SXin Li            else:
71*9c5db199SXin Li                # A proxy generator is passed as an argument so that a proxy
72*9c5db199SXin Li                # could be re-created on demand in ChameleonConnection
73*9c5db199SXin Li                # whenever needed, e.g., after a reboot.
74*9c5db199SXin Li                proxy_generator = (
75*9c5db199SXin Li                        lambda: self.rpc_server_tracker.xmlrpc_connect(
76*9c5db199SXin Li                                None, chameleon_port,
77*9c5db199SXin Li                                ready_test_name=chameleon.CHAMELEON_READY_TEST,
78*9c5db199SXin Li                                timeout_seconds=60))
79*9c5db199SXin Li                self._chameleon_connection = chameleon.ChameleonConnection(
80*9c5db199SXin Li                        None, proxy_generator=proxy_generator)
81*9c5db199SXin Li
82*9c5db199SXin Li        except Exception as e:
83*9c5db199SXin Li            raise ChameleonHostError('Can not connect to Chameleon: %s(%s)',
84*9c5db199SXin Li                                     e.__class__, e)
85*9c5db199SXin Li
86*9c5db199SXin Li
87*9c5db199SXin Li    def _check_if_is_in_lab(self):
88*9c5db199SXin Li        """Checks if Chameleon host is in lab and set self._is_in_lab.
89*9c5db199SXin Li
90*9c5db199SXin Li        If self.hostname is an IP address, we treat it as is not in lab zone.
91*9c5db199SXin Li
92*9c5db199SXin Li        """
93*9c5db199SXin Li        self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname)
94*9c5db199SXin Li                           else utils.host_is_in_lab_zone(self.hostname))
95*9c5db199SXin Li
96*9c5db199SXin Li
97*9c5db199SXin Li    def is_in_lab(self):
98*9c5db199SXin Li        """Check whether the chameleon host is a lab device.
99*9c5db199SXin Li
100*9c5db199SXin Li        @returns: True if the chameleon host is in Cros Lab, otherwise False.
101*9c5db199SXin Li
102*9c5db199SXin Li        """
103*9c5db199SXin Li        return self._is_in_lab
104*9c5db199SXin Li
105*9c5db199SXin Li
106*9c5db199SXin Li    def get_wait_up_processes(self):
107*9c5db199SXin Li        """Get the list of local processes to wait for in wait_up.
108*9c5db199SXin Li
109*9c5db199SXin Li        Override get_wait_up_processes in
110*9c5db199SXin Li        autotest_lib.client.common_lib.hosts.base_classes.Host.
111*9c5db199SXin Li        Wait for chameleond process to go up. Called by base class when
112*9c5db199SXin Li        rebooting the device.
113*9c5db199SXin Li
114*9c5db199SXin Li        """
115*9c5db199SXin Li        processes = [self.CHAMELEOND_PROCESS]
116*9c5db199SXin Li        return processes
117*9c5db199SXin Li
118*9c5db199SXin Li
119*9c5db199SXin Li    def create_chameleon_board(self):
120*9c5db199SXin Li        """Create a ChameleonBoard object with error recovery.
121*9c5db199SXin Li
122*9c5db199SXin Li        This function will reboot the chameleon board once and retry if we can't
123*9c5db199SXin Li        create chameleon board.
124*9c5db199SXin Li
125*9c5db199SXin Li        @return A ChameleonBoard object.
126*9c5db199SXin Li        """
127*9c5db199SXin Li        # TODO(waihong): Add verify and repair logic which are required while
128*9c5db199SXin Li        # deploying to Cros Lab.
129*9c5db199SXin Li        try:
130*9c5db199SXin Li            chameleon_board = chameleon.ChameleonBoard(
131*9c5db199SXin Li                    self._chameleon_connection, self)
132*9c5db199SXin Li            return chameleon_board
133*9c5db199SXin Li        except Exception as e:
134*9c5db199SXin Li            raise ChameleonHostError('Can not create chameleon board: %s(%s)',
135*9c5db199SXin Li                                     e.__class__, e)
136*9c5db199SXin Li
137*9c5db199SXin Li
138*9c5db199SXin Lidef create_chameleon_host(dut, chameleon_args):
139*9c5db199SXin Li    """Create a ChameleonHost object.
140*9c5db199SXin Li
141*9c5db199SXin Li    There three possible cases:
142*9c5db199SXin Li    1) If the DUT is in Cros Lab and has a chameleon board, then create
143*9c5db199SXin Li       a ChameleonHost object pointing to the board. chameleon_args
144*9c5db199SXin Li       is ignored.
145*9c5db199SXin Li    2) If not case 1) and chameleon_args is neither None nor empty, then
146*9c5db199SXin Li       create a ChameleonHost object using chameleon_args.
147*9c5db199SXin Li    3) If neither case 1) or 2) applies, return None.
148*9c5db199SXin Li
149*9c5db199SXin Li    @param dut: host name of the host that chameleon connects. It can be used
150*9c5db199SXin Li                to lookup the chameleon in test lab using naming convention.
151*9c5db199SXin Li                If dut is an IP address, it can not be used to lookup the
152*9c5db199SXin Li                chameleon in test lab.
153*9c5db199SXin Li    @param chameleon_args: A dictionary that contains args for creating
154*9c5db199SXin Li                           a ChameleonHost object,
155*9c5db199SXin Li                           e.g. {'chameleon_host': '172.11.11.112',
156*9c5db199SXin Li                                 'chameleon_port': 9992}.
157*9c5db199SXin Li
158*9c5db199SXin Li    @returns: A ChameleonHost object or None.
159*9c5db199SXin Li
160*9c5db199SXin Li    """
161*9c5db199SXin Li    if not utils.is_in_container():
162*9c5db199SXin Li        is_moblab = utils.is_moblab()
163*9c5db199SXin Li    else:
164*9c5db199SXin Li        is_moblab = _CONFIG.get_config_value(
165*9c5db199SXin Li                'SSP', 'is_moblab', type=bool, default=False)
166*9c5db199SXin Li
167*9c5db199SXin Li    if not is_moblab:
168*9c5db199SXin Li        dut_is_hostname = not dnsname_mangler.is_ip_address(dut)
169*9c5db199SXin Li        if dut_is_hostname:
170*9c5db199SXin Li            chameleon_hostname = chameleon.make_chameleon_hostname(dut)
171*9c5db199SXin Li            if utils.host_is_in_lab_zone(chameleon_hostname):
172*9c5db199SXin Li                # Be more tolerant on chameleon in the lab because
173*9c5db199SXin Li                # we don't want dead chameleon blocks non-chameleon tests.
174*9c5db199SXin Li                # We use ssh ping here as BeyondCorp-only hosts cannot make ICMP
175*9c5db199SXin Li                # ping to chameleon test devices.
176*9c5db199SXin Li                try:
177*9c5db199SXin Li                    ssh_host.SSHHost(chameleon_hostname).ssh_ping()
178*9c5db199SXin Li                except (error.AutoservSSHTimeout,
179*9c5db199SXin Li                        error.AutoservSshPermissionDeniedError,
180*9c5db199SXin Li                        error.AutoservSshPingHostError) as e:
181*9c5db199SXin Li                    logging.warning(
182*9c5db199SXin Li                            'Chameleon %s is not accessible. Please file a bug'
183*9c5db199SXin Li                            ' to test lab: %s', chameleon_hostname, e)
184*9c5db199SXin Li                    return None
185*9c5db199SXin Li                return ChameleonHost(chameleon_host=chameleon_hostname)
186*9c5db199SXin Li        if chameleon_args:
187*9c5db199SXin Li            return ChameleonHost(**chameleon_args)
188*9c5db199SXin Li        else:
189*9c5db199SXin Li            return None
190*9c5db199SXin Li    else:
191*9c5db199SXin Li        afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
192*9c5db199SXin Li        hosts = afe.get_hosts(hostname=dut)
193*9c5db199SXin Li        if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
194*9c5db199SXin Li            return ChameleonHost(
195*9c5db199SXin Li                chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
196*9c5db199SXin Li                chameleon_port=hosts[0].attributes.get(
197*9c5db199SXin Li                    CHAMELEON_PORT_ATTR, 9992)
198*9c5db199SXin Li            )
199*9c5db199SXin Li        else:
200*9c5db199SXin Li            return None
201*9c5db199SXin Li
202*9c5db199SXin Li
203*9c5db199SXin Lidef create_btpeer_host(dut, btpeer_args_list):
204*9c5db199SXin Li    """Create a ChameleonHost object for a Bluetooth peer
205*9c5db199SXin Li
206*9c5db199SXin Li    This is similar to create_chameleon_host but unlike chameleon board
207*9c5db199SXin Li    there can be multiple btpeers with a single DUT
208*9c5db199SXin Li
209*9c5db199SXin Li    There four possible cases:
210*9c5db199SXin Li    1) If the DUT is in Cros Lab then assume that it can have up to 4 bluetooth
211*9c5db199SXin Li       peers. Ping the url and create a Chameleon host for each Bluetooth peer
212*9c5db199SXin Li       present. btpeer_args_list is ignored.
213*9c5db199SXin Li    2) If not case 1) and btpeer_args_list is not empty, then
214*9c5db199SXin Li       create a BtpeerHost object for each host specified in btpeer_args_list.
215*9c5db199SXin Li    3) If neither case 1) or 2) applies, return None.
216*9c5db199SXin Li    4) This DUT is controlled  by moblab. This case is not implemented.
217*9c5db199SXin Li
218*9c5db199SXin Li
219*9c5db199SXin Li    @param dut: host name of the host that btpeer connects. It can be used
220*9c5db199SXin Li                to lookup the btpeer in test lab using naming convention.
221*9c5db199SXin Li                If dut is an IP address, it can not be used to lookup the
222*9c5db199SXin Li                btpeer in test lab. Naming convention in the lab is
223*9c5db199SXin Li                <hostname>-btpeer[1-4]
224*9c5db199SXin Li    @param btpeer_args_list: A list of dictionaries that contains args for
225*9c5db199SXin Li                            creating a BtpeerHost object,
226*9c5db199SXin Li                           e.g. {'btpeer_host': '172.11.11.112',
227*9c5db199SXin Li                                 'btpeer_port': 9992}.
228*9c5db199SXin Li
229*9c5db199SXin Li    @returns: A list of BtpeerHost objects
230*9c5db199SXin Li
231*9c5db199SXin Li    """
232*9c5db199SXin Li    def _convert_btpeer_args(args):
233*9c5db199SXin Li        """Convert btpeer args to format accepted by ChameleonHost."""
234*9c5db199SXin Li        ret_args = {}
235*9c5db199SXin Li        if 'btpeer_host' in args:
236*9c5db199SXin Li            ret_args['chameleon_host'] = args['btpeer_host']
237*9c5db199SXin Li        if 'btpeer_port' in args:
238*9c5db199SXin Li            ret_args['chameleon_port'] = int(args['btpeer_port'])
239*9c5db199SXin Li        if 'btpeer_ssh_port' in args:
240*9c5db199SXin Li            ret_args['port'] = int(args['btpeer_ssh_port'])
241*9c5db199SXin Li        return ret_args
242*9c5db199SXin Li
243*9c5db199SXin Li    if not utils.is_in_container():
244*9c5db199SXin Li        is_moblab = utils.is_moblab()
245*9c5db199SXin Li    else:
246*9c5db199SXin Li        is_moblab = _CONFIG.get_config_value(
247*9c5db199SXin Li                'SSP', 'is_moblab', type=bool, default=False)
248*9c5db199SXin Li
249*9c5db199SXin Li    btpeer_hosts = []
250*9c5db199SXin Li
251*9c5db199SXin Li    if not is_moblab:
252*9c5db199SXin Li        if (not dnsname_mangler.is_ip_address(dut) and
253*9c5db199SXin Li            utils.host_is_in_lab_zone(dut)):
254*9c5db199SXin Li            # This is a device in the lab. Ignore any arguments passed and
255*9c5db199SXin Li            # derive peer hostnames from the DUT hostname
256*9c5db199SXin Li            btpeer_hostnames = chameleon.make_btpeer_hostnames(dut)
257*9c5db199SXin Li            for btpeer_hostname in btpeer_hostnames:
258*9c5db199SXin Li                # Not all test bed have 4 Bluetooth peers
259*9c5db199SXin Li                if utils.ping(btpeer_hostname, deadline=3):
260*9c5db199SXin Li                    logging.warning('Btpeer %s is not accessible. This maybe '
261*9c5db199SXin Li                                    'expected or it maybe an issue with the '
262*9c5db199SXin Li                                    'Bluetooth peer. Please Check the test bed.'
263*9c5db199SXin Li                                    , btpeer_hostname)
264*9c5db199SXin Li                    continue
265*9c5db199SXin Li                else:
266*9c5db199SXin Li                    logging.debug("Creating btpeer from %s",btpeer_hostname)
267*9c5db199SXin Li                    btpeer_hosts.append(
268*9c5db199SXin Li                        ChameleonHost(chameleon_host=btpeer_hostname))
269*9c5db199SXin Li            return btpeer_hosts
270*9c5db199SXin Li        else:
271*9c5db199SXin Li            # IP address given or DNS address is not in lab.
272*9c5db199SXin Li            # Create the Bluetooth peers from the arguments passed
273*9c5db199SXin Li            return [ ChameleonHost(**_convert_btpeer_args(btpeer_args))
274*9c5db199SXin Li                     for btpeer_args in btpeer_args_list]
275*9c5db199SXin Li    else:
276*9c5db199SXin Li        # TODO(b:149606762)
277*9c5db199SXin Li        # moblab still create Bluetooth peer from chameleon_args
278*9c5db199SXin Li        afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
279*9c5db199SXin Li        hosts = afe.get_hosts(hostname=dut)
280*9c5db199SXin Li        if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
281*9c5db199SXin Li            return [ChameleonHost(
282*9c5db199SXin Li                chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
283*9c5db199SXin Li                chameleon_port=hosts[0].attributes.get(
284*9c5db199SXin Li                    CHAMELEON_PORT_ATTR, 9992)
285*9c5db199SXin Li            )]
286*9c5db199SXin Li        else:
287*9c5db199SXin Li            return []
288