• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
24
25class ChameleonHostError(Exception):
26    """Error in ChameleonHost."""
27    pass
28
29
30class ChameleonHost(ssh_host.SSHHost):
31    """Host class for a host that controls a Chameleon."""
32
33    # Chameleond process name.
34    CHAMELEOND_PROCESS = 'chameleond'
35
36
37    # TODO(waihong): Add verify and repair logic which are required while
38    # deploying to Cros Lab.
39
40
41    def _initialize(self, chameleon_host='localhost', chameleon_port=9992,
42                    *args, **dargs):
43        """Initialize a ChameleonHost instance.
44
45        A ChameleonHost instance represents a host that controls a Chameleon.
46
47        @param chameleon_host: Name of the host where the chameleond process
48                               is running.
49                               If this is passed in by IP address, it will be
50                               treated as not in lab.
51        @param chameleon_port: Port the chameleond process is listening on.
52
53        """
54        super(ChameleonHost, self)._initialize(hostname=chameleon_host,
55                                               *args, **dargs)
56
57        self._is_in_lab = None
58        self._check_if_is_in_lab()
59
60        self._chameleon_port = chameleon_port
61        self._local_port = None
62        self._tunneling_process = None
63
64        if self._is_in_lab:
65            self._chameleon_connection = chameleon.ChameleonConnection(
66                    self.hostname, chameleon_port)
67        else:
68            self._create_connection_through_tunnel()
69
70
71    def _check_if_is_in_lab(self):
72        """Checks if Chameleon host is in lab and set self._is_in_lab.
73
74        If self.hostname is an IP address, we treat it as is not in lab zone.
75
76        """
77        self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname)
78                           else utils.host_is_in_lab_zone(self.hostname))
79
80
81    def _create_connection_through_tunnel(self):
82        """Creates Chameleon connection through SSH tunnel.
83
84        For developers to run server side test on corp device against
85        testing device on Google Test Network, it is required to use
86        SSH tunneling to access ports other than SSH port.
87
88        """
89        try:
90            self._local_port = utils.get_unused_port()
91            self._tunneling_process = self._create_ssh_tunnel(
92                    self._chameleon_port, self._local_port)
93
94            self._wait_for_connection_established()
95
96        # Always close tunnel when fail to create connection.
97        except:
98            logging.exception('Error in creating connection through tunnel.')
99            self._disconnect_tunneling()
100            raise
101
102
103    def _wait_for_connection_established(self):
104        """Wait for ChameleonConnection through tunnel being established."""
105
106        def _create_connection():
107            """Create ChameleonConnection.
108
109            @returns: True if success. False otherwise.
110
111            """
112            try:
113                self._chameleon_connection = chameleon.ChameleonConnection(
114                        'localhost', self._local_port)
115            except chameleon.ChameleonConnectionError:
116                logging.debug('Connection is not ready yet ...')
117                return False
118
119            logging.debug('Connection is up')
120            return True
121
122        success = utils.wait_for_value(
123            _create_connection, expected_value=True, timeout_sec=30)
124
125        if not success:
126            raise ChameleonHostError('Can not connect to Chameleon')
127
128
129    def is_in_lab(self):
130        """Check whether the chameleon host is a lab device.
131
132        @returns: True if the chameleon host is in Cros Lab, otherwise False.
133
134        """
135        return self._is_in_lab
136
137
138    def get_wait_up_processes(self):
139        """Get the list of local processes to wait for in wait_up.
140
141        Override get_wait_up_processes in
142        autotest_lib.client.common_lib.hosts.base_classes.Host.
143        Wait for chameleond process to go up. Called by base class when
144        rebooting the device.
145
146        """
147        processes = [self.CHAMELEOND_PROCESS]
148        return processes
149
150
151    def create_chameleon_board(self):
152        """Create a ChameleonBoard object."""
153        # TODO(waihong): Add verify and repair logic which are required while
154        # deploying to Cros Lab.
155        return chameleon.ChameleonBoard(self._chameleon_connection, self)
156
157
158    def _disconnect_tunneling(self):
159        """Disconnect the SSH tunnel."""
160        if not self._tunneling_process:
161            return
162
163        if self._tunneling_process.poll() is None:
164            self._tunneling_process.terminate()
165            logging.debug(
166                    'chameleon_host terminated tunnel, pid %d',
167                    self._tunneling_process.pid)
168        else:
169            logging.debug(
170                    'chameleon_host tunnel pid %d terminated early, status %d',
171                    self._tunneling_process.pid,
172                    self._tunneling_process.returncode)
173
174
175    def close(self):
176        """Cleanup function when ChameleonHost is to be destroyed."""
177        self._disconnect_tunneling()
178        super(ChameleonHost, self).close()
179
180
181def create_chameleon_host(dut, chameleon_args):
182    """Create a ChameleonHost object.
183
184    There three possible cases:
185    1) If the DUT is in Cros Lab and has a chameleon board, then create
186       a ChameleonHost object pointing to the board. chameleon_args
187       is ignored.
188    2) If not case 1) and chameleon_args is neither None nor empty, then
189       create a ChameleonHost object using chameleon_args.
190    3) If neither case 1) or 2) applies, return None.
191
192    @param dut: host name of the host that chameleon connects. It can be used
193                to lookup the chameleon in test lab using naming convention.
194                If dut is an IP address, it can not be used to lookup the
195                chameleon in test lab.
196    @param chameleon_args: A dictionary that contains args for creating
197                           a ChameleonHost object,
198                           e.g. {'chameleon_host': '172.11.11.112',
199                                 'chameleon_port': 9992}.
200
201    @returns: A ChameleonHost object or None.
202
203    """
204    if not utils.is_in_container():
205        is_moblab = utils.is_moblab()
206    else:
207        is_moblab = _CONFIG.get_config_value(
208                'SSP', 'is_moblab', type=bool, default=False)
209
210    if not is_moblab:
211        dut_is_hostname = not dnsname_mangler.is_ip_address(dut)
212        if dut_is_hostname:
213            chameleon_hostname = chameleon.make_chameleon_hostname(dut)
214            if utils.host_is_in_lab_zone(chameleon_hostname):
215                return ChameleonHost(chameleon_host=chameleon_hostname)
216        if chameleon_args:
217            return ChameleonHost(**chameleon_args)
218        else:
219            return None
220    else:
221        afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
222        hosts = afe.get_hosts(hostname=dut)
223        if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
224            return ChameleonHost(
225                chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
226                chameleon_port=hosts[0].attributes.get(
227                    CHAMELEON_PORT_ATTR, 9992)
228            )
229        else:
230            return None
231