1d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng"""Provides a factory method to create a host object."""
2d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
346dadc9439355f72d394dcc4700902001bd797ffbeepsimport logging
45189dd5255f3cd8d68026b081367c32d135dd9b6Aviv Keshetfrom contextlib import closing
546dadc9439355f72d394dcc4700902001bd797ffbeeps
614622bb8249c2200e5258ea6d40b3362229e90a7Simran Basifrom autotest_lib.client.bin import local_host
7c5947faa755945f81537c6c33c322dccacac0adeAviv Keshetfrom autotest_lib.client.common_lib import error, global_config
81c3b853b2d9b80c9efcc1a27112731b96d765e6eJ. Richard Barnettefrom autotest_lib.server import utils as server_utils
91c3b853b2d9b80c9efcc1a27112731b96d765e6eJ. Richard Barnettefrom autotest_lib.server.hosts import cros_host, ssh_host
10e5f7ae44738af9f6138103e7c57c26b415486ce8Simran Basifrom autotest_lib.server.hosts import moblab_host, sonic_host
11549beb41550bf954394ca8c3a611397c97745759Kevin Chengfrom autotest_lib.server.hosts import adb_host, testbed
12e48bcfb879c943df72a866e3db8b2df8e930b2c1mbligh
13d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
1455552bf4a54d847f2cc30b307b3636c2be2ad5a2mblighSSH_ENGINE = global_config.global_config.get_config_value('AUTOSERV',
1555552bf4a54d847f2cc30b307b3636c2be2ad5a2mbligh                                                          'ssh_engine',
1655552bf4a54d847f2cc30b307b3636c2be2ad5a2mbligh                                                          type=str)
17d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
18d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps# Default ssh options used in creating a host.
19d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsDEFAULT_SSH_USER = 'root'
20d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsDEFAULT_SSH_PASS = ''
21d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsDEFAULT_SSH_PORT = 22
22d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsDEFAULT_SSH_VERBOSITY = ''
23d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsDEFAULT_SSH_OPTIONS = ''
24d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
25d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski# for tracking which hostnames have already had job_start called
26d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski_started_hostnames = set()
27d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski
2846dadc9439355f72d394dcc4700902001bd797ffbeeps# A list of all the possible host types, ordered according to frequency of
2946dadc9439355f72d394dcc4700902001bd797ffbeeps# host types in the lab, so the more common hosts don't incur a repeated ssh
3046dadc9439355f72d394dcc4700902001bd797ffbeeps# overhead in checking for less common host types.
31e5f7ae44738af9f6138103e7c57c26b415486ce8Simran Basihost_types = [cros_host.CrosHost, moblab_host.MoblabHost, sonic_host.SonicHost,
32e5f7ae44738af9f6138103e7c57c26b415486ce8Simran Basi              adb_host.ADBHost,]
3314622bb8249c2200e5258ea6d40b3362229e90a7Simran BasiOS_HOST_DICT = {'cros' : cros_host.CrosHost,
3414622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi                'android': adb_host.ADBHost}
3546dadc9439355f72d394dcc4700902001bd797ffbeeps
36d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
37d0672689266d3d63901f3a35fb7e0a2d96d6e39abeepsdef _get_host_arguments():
38d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    """Returns parameters needed to ssh into a host.
39d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
40d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    There are currently 2 use cases for creating a host.
41d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    1. Through the server_job, in which case the server_job injects
42d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps       the appropriate ssh parameters into our name space and they
43d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps       are available as the variables ssh_user, ssh_pass etc.
44d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    2. Directly through factory.create_host, in which case we use
45d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps       the same defaults as used in the server job to create a host.
46d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
47d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    @returns: A tuple of parameters needed to create an ssh connection, ordered
48d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps              as: ssh_user, ssh_pass, ssh_port, ssh_verbosity, ssh_options.
49d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    """
50d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    g = globals()
51d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps    return (g.get('ssh_user', DEFAULT_SSH_USER),
52d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps            g.get('ssh_pass', DEFAULT_SSH_PASS),
53d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps            g.get('ssh_port', DEFAULT_SSH_PORT),
54d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps            g.get('ssh_verbosity_flag', DEFAULT_SSH_VERBOSITY),
55d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps            g.get('ssh_options', DEFAULT_SSH_OPTIONS))
56d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
57d0672689266d3d63901f3a35fb7e0a2d96d6e39abeeps
58724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basidef _detect_host(connectivity_class, hostname, **args):
59724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    """Detect host type.
60724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
6146dadc9439355f72d394dcc4700902001bd797ffbeeps    Goes through all the possible host classes, calling check_host with a
6246dadc9439355f72d394dcc4700902001bd797ffbeeps    basic host object. Currently this is an ssh host, but theoretically it
6346dadc9439355f72d394dcc4700902001bd797ffbeeps    can be any host object that the check_host method of appropriate host
6446dadc9439355f72d394dcc4700902001bd797ffbeeps    type knows to use.
65724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
66724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    @param connectivity_class: connectivity class to use to talk to the host
67724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi                               (ParamikoHost or SSHHost)
68724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    @param hostname: A string representing the host name of the device.
69724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    @param args: Args that will be passed to the constructor of
70724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi                 the host class.
71724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
7246dadc9439355f72d394dcc4700902001bd797ffbeeps    @returns: Class type of the first host class that returns True to the
7346dadc9439355f72d394dcc4700902001bd797ffbeeps              check_host method.
74724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    """
7546dadc9439355f72d394dcc4700902001bd797ffbeeps    # TODO crbug.com/302026 (sbasi) - adjust this pathway for ADBHost in
7646dadc9439355f72d394dcc4700902001bd797ffbeeps    # the future should a host require verify/repair.
7746dadc9439355f72d394dcc4700902001bd797ffbeeps    with closing(connectivity_class(hostname, **args)) as host:
7846dadc9439355f72d394dcc4700902001bd797ffbeeps        for host_module in host_types:
7946dadc9439355f72d394dcc4700902001bd797ffbeeps            if host_module.check_host(host, timeout=10):
8046dadc9439355f72d394dcc4700902001bd797ffbeeps                return host_module
8146dadc9439355f72d394dcc4700902001bd797ffbeeps
8246dadc9439355f72d394dcc4700902001bd797ffbeeps    logging.warning('Unable to apply conventional host detection methods, '
8346dadc9439355f72d394dcc4700902001bd797ffbeeps                    'defaulting to chromeos host.')
8446dadc9439355f72d394dcc4700902001bd797ffbeeps    return cros_host.CrosHost
85724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
86724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
870e72735393422ec11696d836f0756d96b0497380Gwendal Grignoudef _choose_connectivity_class(hostname, ssh_port):
882a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    """Choose a connectivity class for this hostname.
892a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi
902a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    @param hostname: hostname that we need a connectivity class for.
910e72735393422ec11696d836f0756d96b0497380Gwendal Grignou    @param ssh_port: SSH port to connect to the host.
922a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi
932a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    @returns a connectivity host class.
942a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    """
950e72735393422ec11696d836f0756d96b0497380Gwendal Grignou    if (hostname == 'localhost' and ssh_port == DEFAULT_SSH_PORT):
962a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        return local_host.LocalHost
972a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    # by default assume we're using SSH support
982a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    elif SSH_ENGINE == 'paramiko':
992a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        # Not all systems have paramiko installed so only import paramiko host
1002a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        # if the global_config settings call for it.
1012a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        from autotest_lib.server.hosts import paramiko_host
1022a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        return paramiko_host.ParamikoHost
1032a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    elif SSH_ENGINE == 'raw_ssh':
1042a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        return ssh_host.SSHHost
1052a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    else:
1062a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi        raise error.AutoServError("Unknown SSH engine %s. Please verify the "
1072a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi                                  "value of the configuration key 'ssh_engine' "
1082a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi                                  "on autotest's global_config.ini file." %
1092a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi                                  SSH_ENGINE)
1102a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi
1112a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi
11285e864a0b6e00c2c2de0bbb698ff927822916a0eKevin Cheng# TODO(kevcheng): Update the creation method so it's not a research project
11385e864a0b6e00c2c2de0bbb698ff927822916a0eKevin Cheng# determining the class inheritance model.
1142a5dc2568ec03419c46efe1752287e24d660a02fSimran Basidef create_host(machine, host_class=None, connectivity_class=None, **args):
115d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    """Create a host object.
116d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
117d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    This method mixes host classes that are needed into a new subclass
118d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    and creates a instance of the new class.
119d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
1201bf60eb788365f083d0ee8045a6556f906149decSimran Basi    @param machine: A dict representing the device under test or a String
1211bf60eb788365f083d0ee8045a6556f906149decSimran Basi                    representing the DUT hostname (for legacy caller support).
1221bf60eb788365f083d0ee8045a6556f906149decSimran Basi                    If it is a machine dict, the 'hostname' key is required.
1231bf60eb788365f083d0ee8045a6556f906149decSimran Basi                    Optional 'host_attributes' key will pipe in host_attributes
1241bf60eb788365f083d0ee8045a6556f906149decSimran Basi                    from the autoserv runtime or the AFE.
125a5522a35b55c7cac915cf0b4efc69ead32e26d35Simran Basi    @param host_class: Host class to use, if None, will attempt to detect
126a5522a35b55c7cac915cf0b4efc69ead32e26d35Simran Basi                       the correct class.
1272a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    @param connectivity_class: Connectivity class to use, if None will decide
1282a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi                               based off of hostname and config settings.
129d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    @param args: Args that will be passed to the constructor of
130d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng                 the new host class.
131d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng
132d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    @returns: A host object which is an instance of the newly created
133d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng              host class.
134d1c2b73b7a1cbc74701ddc2b6bbbb3a162ace047Fang Deng    """
1351bf60eb788365f083d0ee8045a6556f906149decSimran Basi    hostname, host_attributes = server_utils.get_host_info_from_machine(
1361bf60eb788365f083d0ee8045a6556f906149decSimran Basi            machine)
1371bf60eb788365f083d0ee8045a6556f906149decSimran Basi    args['host_attributes'] = host_attributes
138724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    ssh_user, ssh_pass, ssh_port, ssh_verbosity_flag, ssh_options = \
139724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi            _get_host_arguments()
140724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi
1410e72735393422ec11696d836f0756d96b0497380Gwendal Grignou    hostname, args['user'], args['password'], ssh_port = \
142724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi            server_utils.parse_machine(hostname, ssh_user, ssh_pass, ssh_port)
143724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    args['ssh_verbosity_flag'] = ssh_verbosity_flag
144724b8a50f34b85dda9f1990e3e8c5b68edbdb37bSimran Basi    args['ssh_options'] = ssh_options
1450e72735393422ec11696d836f0756d96b0497380Gwendal Grignou    args['port'] = ssh_port
146431010f31bceddcf92f36e1b82397b9a56ffee54Simran Basi
1472a5dc2568ec03419c46efe1752287e24d660a02fSimran Basi    if not connectivity_class:
1480e72735393422ec11696d836f0756d96b0497380Gwendal Grignou        connectivity_class = _choose_connectivity_class(hostname, ssh_port)
14914622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi    host_attributes = args.get('host_attributes', {})
15014622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi    host_class = host_class or OS_HOST_DICT.get(host_attributes.get('os_type'))
15114622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi    if host_class:
15214622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi        classes = [host_class, connectivity_class]
15314622bb8249c2200e5258ea6d40b3362229e90a7Simran Basi    else:
154a5522a35b55c7cac915cf0b4efc69ead32e26d35Simran Basi        classes = [_detect_host(connectivity_class, hostname, **args),
155a5522a35b55c7cac915cf0b4efc69ead32e26d35Simran Basi                   connectivity_class]
156635b06f6c016fd5e4e14e98c471e92b1f435ac46jadmanski
157635b06f6c016fd5e4e14e98c471e92b1f435ac46jadmanski    # create a custom host class for this machine and return an instance of it
158635b06f6c016fd5e4e14e98c471e92b1f435ac46jadmanski    host_class = type("%s_host" % hostname, tuple(classes), {})
159d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski    host_instance = host_class(hostname, **args)
160d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski
161d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski    # call job_start if this is the first time this host is being used
162d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski    if hostname not in _started_hostnames:
163d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski        host_instance.job_start()
164d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski        _started_hostnames.add(hostname)
165d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski
166d60321a8b012aa6101bcb237341fab5bd2d25a04jadmanski    return host_instance
167549beb41550bf954394ca8c3a611397c97745759Kevin Cheng
168549beb41550bf954394ca8c3a611397c97745759Kevin Cheng
16952c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basidef create_testbed(machine, **kwargs):
170549beb41550bf954394ca8c3a611397c97745759Kevin Cheng    """Create the testbed object.
171549beb41550bf954394ca8c3a611397c97745759Kevin Cheng
17252c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi    @param machine: A dict representing the test bed under test or a String
17352c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi                    representing the testbed hostname (for legacy caller
17452c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi                    support).
17552c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi                    If it is a machine dict, the 'hostname' key is required.
17652c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi                    Optional 'host_attributes' key will pipe in host_attributes
17752c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi                    from the autoserv runtime or the AFE.
178debeb68446967a4319e1d7f9868a8b9abafce944Simran Basi    @param kwargs: Keyword args to pass to the testbed initialization.
179549beb41550bf954394ca8c3a611397c97745759Kevin Cheng
180549beb41550bf954394ca8c3a611397c97745759Kevin Cheng    @returns: The testbed object with all associated host objects instantiated.
181549beb41550bf954394ca8c3a611397c97745759Kevin Cheng    """
18252c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi    hostname, host_attributes = server_utils.get_host_info_from_machine(
18352c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi            machine)
18452c785e0ac4ed92f54a88255fe33f45c235b22e3Simran Basi    kwargs['host_attributes'] = host_attributes
1853b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    return testbed.TestBed(hostname, **kwargs)
1863b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng
1873b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng
1883b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Chengdef create_target_machine(machine, **kwargs):
1893b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    """Create the target machine which could be a testbed or a *Host.
1903b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng
1913b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    @param machine: A dict representing the test bed under test or a String
1923b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng                    representing the testbed hostname (for legacy caller
1933b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng                    support).
1943b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng                    If it is a machine dict, the 'hostname' key is required.
1953b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng                    Optional 'host_attributes' key will pipe in host_attributes
1963b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng                    from the autoserv runtime or the AFE.
1973b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    @param kwargs: Keyword args to pass to the testbed initialization.
1983b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng
1993b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    @returns: The target machine to be used for verify/repair.
2003b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    """
2013b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    # TODO(kevcheng): We'll want to have a smarter way of figuring out which
2023b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    # host to create (checking host labels).
2033b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    if server_utils.machine_is_testbed(machine):
2043b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng        return create_testbed(machine, **kwargs)
2053b11181c9ea282d584663d362bcfe2d1b4f9ba51Kevin Cheng    return create_host(machine, **kwargs)
206