1# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4#
5
6"""This file provides core logic for connecting a Chameleon Daemon."""
7
8import logging
9
10from autotest_lib.client.bin import utils
11from autotest_lib.client.common_lib import global_config
12from autotest_lib.client.cros.chameleon import chameleon
13from autotest_lib.server.cros import dnsname_mangler
14from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
15from autotest_lib.server.hosts import ssh_host
16
17
18# Names of the host attributes in the database that represent the values for
19# the chameleon_host and chameleon_port for a servo connected to the DUT.
20CHAMELEON_HOST_ATTR = 'chameleon_host'
21CHAMELEON_PORT_ATTR = 'chameleon_port'
22
23_CONFIG = global_config.global_config
24ENABLE_SSH_TUNNEL_FOR_CHAMELEON = _CONFIG.get_config_value(
25        'CROS', 'enable_ssh_tunnel_for_chameleon', type=bool, default=False)
26
27class ChameleonHostError(Exception):
28    """Error in ChameleonHost."""
29    pass
30
31
32class ChameleonHost(ssh_host.SSHHost):
33    """Host class for a host that controls a Chameleon."""
34
35    # Chameleond process name.
36    CHAMELEOND_PROCESS = 'chameleond'
37
38
39    # TODO(waihong): Add verify and repair logic which are required while
40    # deploying to Cros Lab.
41
42
43    def _initialize(self, chameleon_host='localhost', chameleon_port=9992,
44                    *args, **dargs):
45        """Initialize a ChameleonHost instance.
46
47        A ChameleonHost instance represents a host that controls a Chameleon.
48
49        @param chameleon_host: Name of the host where the chameleond process
50                               is running.
51                               If this is passed in by IP address, it will be
52                               treated as not in lab.
53        @param chameleon_port: Port the chameleond process is listening on.
54
55        """
56        super(ChameleonHost, self)._initialize(hostname=chameleon_host,
57                                               *args, **dargs)
58
59        self._is_in_lab = None
60        self._check_if_is_in_lab()
61
62        self._chameleon_port = chameleon_port
63        self._local_port = None
64        self._tunneling_process = None
65
66        try:
67            if self._is_in_lab and not ENABLE_SSH_TUNNEL_FOR_CHAMELEON:
68                self._chameleon_connection = chameleon.ChameleonConnection(
69                        self.hostname, chameleon_port)
70            else:
71                # A proxy generator is passed as an argument so that a proxy
72                # could be re-created on demand in ChameleonConnection
73                # whenever needed, e.g., after a reboot.
74                proxy_generator = (
75                        lambda: self.rpc_server_tracker.xmlrpc_connect(
76                                None, chameleon_port,
77                                ready_test_name=chameleon.CHAMELEON_READY_TEST,
78                                timeout_seconds=60))
79                self._chameleon_connection = chameleon.ChameleonConnection(
80                        None, proxy_generator=proxy_generator)
81
82        except Exception as e:
83            raise ChameleonHostError('Can not connect to Chameleon: %s(%s)',
84                                     e.__class__, e)
85
86
87    def _check_if_is_in_lab(self):
88        """Checks if Chameleon host is in lab and set self._is_in_lab.
89
90        If self.hostname is an IP address, we treat it as is not in lab zone.
91
92        """
93        self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname)
94                           else utils.host_is_in_lab_zone(self.hostname))
95
96
97    def is_in_lab(self):
98        """Check whether the chameleon host is a lab device.
99
100        @returns: True if the chameleon host is in Cros Lab, otherwise False.
101
102        """
103        return self._is_in_lab
104
105
106    def get_wait_up_processes(self):
107        """Get the list of local processes to wait for in wait_up.
108
109        Override get_wait_up_processes in
110        autotest_lib.client.common_lib.hosts.base_classes.Host.
111        Wait for chameleond process to go up. Called by base class when
112        rebooting the device.
113
114        """
115        processes = [self.CHAMELEOND_PROCESS]
116        return processes
117
118
119    def create_chameleon_board(self):
120        """Create a ChameleonBoard object with error recovery.
121
122        This function will reboot the chameleon board once and retry if we can't
123        create chameleon board.
124
125        @return A ChameleonBoard object.
126        """
127        # TODO(waihong): Add verify and repair logic which are required while
128        # deploying to Cros Lab.
129        chameleon_board = None
130        try:
131            chameleon_board = chameleon.ChameleonBoard(
132                    self._chameleon_connection, self)
133            return chameleon_board
134        except:
135            self.reboot()
136            chameleon_board = chameleon.ChameleonBoard(
137                self._chameleon_connection, self)
138            return chameleon_board
139
140
141def create_chameleon_host(dut, chameleon_args):
142    """Create a ChameleonHost object.
143
144    There three possible cases:
145    1) If the DUT is in Cros Lab and has a chameleon board, then create
146       a ChameleonHost object pointing to the board. chameleon_args
147       is ignored.
148    2) If not case 1) and chameleon_args is neither None nor empty, then
149       create a ChameleonHost object using chameleon_args.
150    3) If neither case 1) or 2) applies, return None.
151
152    @param dut: host name of the host that chameleon connects. It can be used
153                to lookup the chameleon in test lab using naming convention.
154                If dut is an IP address, it can not be used to lookup the
155                chameleon in test lab.
156    @param chameleon_args: A dictionary that contains args for creating
157                           a ChameleonHost object,
158                           e.g. {'chameleon_host': '172.11.11.112',
159                                 'chameleon_port': 9992}.
160
161    @returns: A ChameleonHost object or None.
162
163    """
164    if not utils.is_in_container():
165        is_moblab = utils.is_moblab()
166    else:
167        is_moblab = _CONFIG.get_config_value(
168                'SSP', 'is_moblab', type=bool, default=False)
169
170    if not is_moblab:
171        dut_is_hostname = not dnsname_mangler.is_ip_address(dut)
172        if dut_is_hostname:
173            chameleon_hostname = chameleon.make_chameleon_hostname(dut)
174            if utils.host_is_in_lab_zone(chameleon_hostname):
175                # Be more tolerant on chameleon in the lab because
176                # we don't want dead chameleon blocks non-chameleon tests.
177                if utils.ping(chameleon_hostname, deadline=3):
178                   logging.warning(
179                           'Chameleon %s is not accessible. Please file a bug'
180                           ' to test lab', chameleon_hostname)
181                   return None
182                return ChameleonHost(chameleon_host=chameleon_hostname)
183        if chameleon_args:
184            return ChameleonHost(**chameleon_args)
185        else:
186            return None
187    else:
188        afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
189        hosts = afe.get_hosts(hostname=dut)
190        if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
191            return ChameleonHost(
192                chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
193                chameleon_port=hosts[0].attributes.get(
194                    CHAMELEON_PORT_ATTR, 9992)
195            )
196        else:
197            return None
198