230 lines
7.9 KiB
Python
230 lines
7.9 KiB
Python
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
#
|
|
|
|
"""This file provides core logic for connecting a Chameleon Daemon."""
|
|
|
|
import logging
|
|
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.common_lib import global_config
|
|
from autotest_lib.client.cros.chameleon import chameleon
|
|
from autotest_lib.server.cros import dnsname_mangler
|
|
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
|
|
from autotest_lib.server.hosts import ssh_host
|
|
|
|
|
|
# Names of the host attributes in the database that represent the values for
|
|
# the chameleon_host and chameleon_port for a servo connected to the DUT.
|
|
CHAMELEON_HOST_ATTR = 'chameleon_host'
|
|
CHAMELEON_PORT_ATTR = 'chameleon_port'
|
|
|
|
_CONFIG = global_config.global_config
|
|
|
|
class ChameleonHostError(Exception):
|
|
"""Error in ChameleonHost."""
|
|
pass
|
|
|
|
|
|
class ChameleonHost(ssh_host.SSHHost):
|
|
"""Host class for a host that controls a Chameleon."""
|
|
|
|
# Chameleond process name.
|
|
CHAMELEOND_PROCESS = 'chameleond'
|
|
|
|
|
|
# TODO(waihong): Add verify and repair logic which are required while
|
|
# deploying to Cros Lab.
|
|
|
|
|
|
def _initialize(self, chameleon_host='localhost', chameleon_port=9992,
|
|
*args, **dargs):
|
|
"""Initialize a ChameleonHost instance.
|
|
|
|
A ChameleonHost instance represents a host that controls a Chameleon.
|
|
|
|
@param chameleon_host: Name of the host where the chameleond process
|
|
is running.
|
|
If this is passed in by IP address, it will be
|
|
treated as not in lab.
|
|
@param chameleon_port: Port the chameleond process is listening on.
|
|
|
|
"""
|
|
super(ChameleonHost, self)._initialize(hostname=chameleon_host,
|
|
*args, **dargs)
|
|
|
|
self._is_in_lab = None
|
|
self._check_if_is_in_lab()
|
|
|
|
self._chameleon_port = chameleon_port
|
|
self._local_port = None
|
|
self._tunneling_process = None
|
|
|
|
if self._is_in_lab:
|
|
self._chameleon_connection = chameleon.ChameleonConnection(
|
|
self.hostname, chameleon_port)
|
|
else:
|
|
self._create_connection_through_tunnel()
|
|
|
|
|
|
def _check_if_is_in_lab(self):
|
|
"""Checks if Chameleon host is in lab and set self._is_in_lab.
|
|
|
|
If self.hostname is an IP address, we treat it as is not in lab zone.
|
|
|
|
"""
|
|
self._is_in_lab = (False if dnsname_mangler.is_ip_address(self.hostname)
|
|
else utils.host_is_in_lab_zone(self.hostname))
|
|
|
|
|
|
def _create_connection_through_tunnel(self):
|
|
"""Creates Chameleon connection through SSH tunnel.
|
|
|
|
For developers to run server side test on corp device against
|
|
testing device on Google Test Network, it is required to use
|
|
SSH tunneling to access ports other than SSH port.
|
|
|
|
"""
|
|
try:
|
|
self._local_port = utils.get_unused_port()
|
|
self._tunneling_process = self._create_ssh_tunnel(
|
|
self._chameleon_port, self._local_port)
|
|
|
|
self._wait_for_connection_established()
|
|
|
|
# Always close tunnel when fail to create connection.
|
|
except:
|
|
logging.exception('Error in creating connection through tunnel.')
|
|
self._disconnect_tunneling()
|
|
raise
|
|
|
|
|
|
def _wait_for_connection_established(self):
|
|
"""Wait for ChameleonConnection through tunnel being established."""
|
|
|
|
def _create_connection():
|
|
"""Create ChameleonConnection.
|
|
|
|
@returns: True if success. False otherwise.
|
|
|
|
"""
|
|
try:
|
|
self._chameleon_connection = chameleon.ChameleonConnection(
|
|
'localhost', self._local_port)
|
|
except chameleon.ChameleonConnectionError:
|
|
logging.debug('Connection is not ready yet ...')
|
|
return False
|
|
|
|
logging.debug('Connection is up')
|
|
return True
|
|
|
|
success = utils.wait_for_value(
|
|
_create_connection, expected_value=True, timeout_sec=30)
|
|
|
|
if not success:
|
|
raise ChameleonHostError('Can not connect to Chameleon')
|
|
|
|
|
|
def is_in_lab(self):
|
|
"""Check whether the chameleon host is a lab device.
|
|
|
|
@returns: True if the chameleon host is in Cros Lab, otherwise False.
|
|
|
|
"""
|
|
return self._is_in_lab
|
|
|
|
|
|
def get_wait_up_processes(self):
|
|
"""Get the list of local processes to wait for in wait_up.
|
|
|
|
Override get_wait_up_processes in
|
|
autotest_lib.client.common_lib.hosts.base_classes.Host.
|
|
Wait for chameleond process to go up. Called by base class when
|
|
rebooting the device.
|
|
|
|
"""
|
|
processes = [self.CHAMELEOND_PROCESS]
|
|
return processes
|
|
|
|
|
|
def create_chameleon_board(self):
|
|
"""Create a ChameleonBoard object."""
|
|
# TODO(waihong): Add verify and repair logic which are required while
|
|
# deploying to Cros Lab.
|
|
return chameleon.ChameleonBoard(self._chameleon_connection, self)
|
|
|
|
|
|
def _disconnect_tunneling(self):
|
|
"""Disconnect the SSH tunnel."""
|
|
if not self._tunneling_process:
|
|
return
|
|
|
|
if self._tunneling_process.poll() is None:
|
|
self._tunneling_process.terminate()
|
|
logging.debug(
|
|
'chameleon_host terminated tunnel, pid %d',
|
|
self._tunneling_process.pid)
|
|
else:
|
|
logging.debug(
|
|
'chameleon_host tunnel pid %d terminated early, status %d',
|
|
self._tunneling_process.pid,
|
|
self._tunneling_process.returncode)
|
|
|
|
|
|
def close(self):
|
|
"""Cleanup function when ChameleonHost is to be destroyed."""
|
|
self._disconnect_tunneling()
|
|
super(ChameleonHost, self).close()
|
|
|
|
|
|
def create_chameleon_host(dut, chameleon_args):
|
|
"""Create a ChameleonHost object.
|
|
|
|
There three possible cases:
|
|
1) If the DUT is in Cros Lab and has a chameleon board, then create
|
|
a ChameleonHost object pointing to the board. chameleon_args
|
|
is ignored.
|
|
2) If not case 1) and chameleon_args is neither None nor empty, then
|
|
create a ChameleonHost object using chameleon_args.
|
|
3) If neither case 1) or 2) applies, return None.
|
|
|
|
@param dut: host name of the host that chameleon connects. It can be used
|
|
to lookup the chameleon in test lab using naming convention.
|
|
If dut is an IP address, it can not be used to lookup the
|
|
chameleon in test lab.
|
|
@param chameleon_args: A dictionary that contains args for creating
|
|
a ChameleonHost object,
|
|
e.g. {'chameleon_host': '172.11.11.112',
|
|
'chameleon_port': 9992}.
|
|
|
|
@returns: A ChameleonHost object or None.
|
|
|
|
"""
|
|
if not utils.is_in_container():
|
|
is_moblab = utils.is_moblab()
|
|
else:
|
|
is_moblab = _CONFIG.get_config_value(
|
|
'SSP', 'is_moblab', type=bool, default=False)
|
|
|
|
if not is_moblab:
|
|
dut_is_hostname = not dnsname_mangler.is_ip_address(dut)
|
|
if dut_is_hostname:
|
|
chameleon_hostname = chameleon.make_chameleon_hostname(dut)
|
|
if utils.host_is_in_lab_zone(chameleon_hostname):
|
|
return ChameleonHost(chameleon_host=chameleon_hostname)
|
|
if chameleon_args:
|
|
return ChameleonHost(**chameleon_args)
|
|
else:
|
|
return None
|
|
else:
|
|
afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
|
|
hosts = afe.get_hosts(hostname=dut)
|
|
if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes:
|
|
return ChameleonHost(
|
|
chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR],
|
|
chameleon_port=hosts[0].attributes.get(
|
|
CHAMELEON_PORT_ATTR, 9992)
|
|
)
|
|
else:
|
|
return None
|