autotest: Add jetstream host autodetection.

The jetstream_host model is currently forced using the os_type attribute.
This change adds autodetection of jetstream hosts so that the normal
host autodection flow can be used with jetstream devices.

BUG=b:28453441
TEST=Patched to moblab, verified host autodetection after removing
    the os_type attribute and os label.

Change-Id: I9d4217da99ce18f2a91de16bbae77af7819a8a14
Reviewed-on: https://chromium-review.googlesource.com/528487
Commit-Ready: Laurence Goodby <lgoodby@chromium.org>
Tested-by: Laurence Goodby <lgoodby@chromium.org>
Reviewed-by: Prathmesh Prabhu <pprabhu@chromium.org>
diff --git a/client/common_lib/lsbrelease_utils.py b/client/common_lib/lsbrelease_utils.py
index 743935c..5b09299 100644
--- a/client/common_lib/lsbrelease_utils.py
+++ b/client/common_lib/lsbrelease_utils.py
@@ -13,6 +13,9 @@
 from autotest_lib.client.cros import constants
 
 
+JETSTREAM_BOARDS = frozenset(['arkham', 'gale', 'whirlwind'])
+
+
 def _lsbrelease_search(regex, group_id=0, lsb_release_content=None):
     """Searches /etc/lsb-release for a regex match.
 
@@ -94,6 +97,18 @@
         logging.error('Unable to determine if this is a moblab system: %s', e)
 
 
+def is_jetstream(lsb_release_content=None):
+    """Parses lsb_contents to determine if the host is a Jetstream host.
+
+    @param lsb_release_content: The string contents of lsb-release.
+            If None, the local lsb-release is used.
+
+    @return True if the host is a Jetstream device, otherwise False.
+    """
+    board = get_current_board(lsb_release_content=lsb_release_content)
+    return board in JETSTREAM_BOARDS
+
+
 def get_chrome_milestone(lsb_release_content=None):
     """Get the value for the Chrome milestone.
 
diff --git a/client/common_lib/lsbrelease_utils_unittest.py b/client/common_lib/lsbrelease_utils_unittest.py
new file mode 100755
index 0000000..583bab6
--- /dev/null
+++ b/client/common_lib/lsbrelease_utils_unittest.py
@@ -0,0 +1,112 @@
+#!/usr/bin/python2
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unittests for the lsbrelease_utils module."""
+
+import unittest
+
+import common
+from autotest_lib.client.common_lib import lsbrelease_utils
+
+
+# pylint: disable=line-too-long
+_GUADO_MOBLAB_LSB_RELEASE_REDACTED = """
+DEVICETYPE=CHROMEBOX
+CHROMEOS_RELEASE_BUILDER_PATH=guado_moblab-release/R61-9641.0.0
+GOOGLE_RELEASE=9641.0.0
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_BOARD=guado_moblab
+CHROMEOS_RELEASE_BUILD_NUMBER=9641
+CHROMEOS_RELEASE_BRANCH_NUMBER=0
+CHROMEOS_RELEASE_CHROME_MILESTONE=61
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=testimage-channel
+CHROMEOS_RELEASE_DESCRIPTION=9641.0.0 (Official Build) dev-channel guado_moblab test
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_VERSION=9641.0.0
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+"""
+
+_LINK_LSB_RELEASE_REDACTED = """
+DEVICETYPE=CHROMEBOOK
+CHROMEOS_RELEASE_BUILDER_PATH=link-release/R61-9641.0.0
+GOOGLE_RELEASE=9641.0.0
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_BOARD=link
+CHROMEOS_RELEASE_BUILD_NUMBER=9641
+CHROMEOS_RELEASE_BRANCH_NUMBER=0
+CHROMEOS_RELEASE_CHROME_MILESTONE=61
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=testimage-channel
+CHROMEOS_RELEASE_DESCRIPTION=9641.0.0 (Official Build) dev-channel link test
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_VERSION=9641.0.0
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+"""
+
+_GALE_LSB_RELEASE_REDACTED = """
+DEVICETYPE=OTHER
+HWID_OVERRIDE=GALE DOGFOOD
+CHROMEOS_RELEASE_BUILDER_PATH=gale-release/R61-9641.0.0
+GOOGLE_RELEASE=9641.0.0
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_BOARD=gale
+CHROMEOS_RELEASE_BUILD_NUMBER=9641
+CHROMEOS_RELEASE_BRANCH_NUMBER=0
+CHROMEOS_RELEASE_CHROME_MILESTONE=61
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=testimage-channel
+CHROMEOS_RELEASE_DESCRIPTION=9641.0.0 (Official Build) dev-channel gale test
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_VERSION=9641.0.0
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+"""
+
+# pylint: disable=line-too-long
+_WHIRLWIND_LSB_RELEASE_REDACTED = """
+DEVICETYPE=OTHER
+HWID_OVERRIDE=WHIRLWIND DOGFOOD
+CHROMEOS_RELEASE_BUILDER_PATH=whirlwind-release/R61-9641.0.0
+GOOGLE_RELEASE=9641.0.0
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_BOARD=whirlwind
+CHROMEOS_RELEASE_BUILD_NUMBER=9641
+CHROMEOS_RELEASE_BRANCH_NUMBER=0
+CHROMEOS_RELEASE_CHROME_MILESTONE=61
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=testimage-channel
+CHROMEOS_RELEASE_DESCRIPTION=9641.0.0 (Official Build) dev-channel whirlwind test
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_VERSION=9641.0.0
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+"""
+
+
+class LsbreleaseUtilsTestCase(unittest.TestCase):
+    """Validates the helper free functions in lsbrelease_utils."""
+
+    def test_is_jetstream_with_link_lsbrelease(self):
+        self.assertFalse(lsbrelease_utils.is_jetstream(
+            _LINK_LSB_RELEASE_REDACTED))
+
+    def test_is_jetstream_with_moblab_lsbrelease(self):
+        self.assertFalse(lsbrelease_utils.is_jetstream(
+            _GUADO_MOBLAB_LSB_RELEASE_REDACTED))
+
+    def test_is_jestream_with_gale_lsbrelease(self):
+        self.assertTrue(lsbrelease_utils.is_jetstream(
+            _GALE_LSB_RELEASE_REDACTED))
+
+    def test_is_jestream_with_whirlwind_lsbrelease(self):
+        self.assertTrue(lsbrelease_utils.is_jetstream(
+            _WHIRLWIND_LSB_RELEASE_REDACTED))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/server/hosts/cros_host.py b/server/hosts/cros_host.py
index 5b2e10a..f302c65 100644
--- a/server/hosts/cros_host.py
+++ b/server/hosts/cros_host.py
@@ -192,9 +192,16 @@
                     '! test -f /mnt/stateful_partition/.android_tester && '
                     '! grep -q moblab /etc/lsb-release',
                     ignore_status=True, timeout=timeout)
+            if result.exit_status == 0:
+                lsb_release_content = host.run(
+                    'grep CHROMEOS_RELEASE_BOARD /etc/lsb-release',
+                    timeout=timeout).stdout
+                return not lsbrelease_utils.is_jetstream(
+                    lsb_release_content=lsb_release_content)
         except (error.AutoservRunError, error.AutoservSSHTimeout):
             return False
-        return result.exit_status == 0
+
+        return False
 
 
     @staticmethod
@@ -824,10 +831,7 @@
         Subclasses may override this to perform any special actions
         required before updating.
         """
-        # Stop service ap-update-manager to prevent rebooting during autoupdate.
-        # The service is used in jetstream boards, but not other CrOS devices.
-        # TODO(lgoodby): Remove this when jetstream hosts are specialized.
-        self.run('sudo stop ap-update-manager', ignore_status=True)
+        pass
 
 
     def machine_install(self, update_url=None, force_update=False,
diff --git a/server/hosts/factory.py b/server/hosts/factory.py
index 6a6cca8..a980849 100644
--- a/server/hosts/factory.py
+++ b/server/hosts/factory.py
@@ -38,7 +38,8 @@
 # A list of all the possible host types, ordered according to frequency of
 # host types in the lab, so the more common hosts don't incur a repeated ssh
 # overhead in checking for less common host types.
-host_types = [cros_host.CrosHost, moblab_host.MoblabHost, sonic_host.SonicHost,
+host_types = [cros_host.CrosHost, moblab_host.MoblabHost,
+              jetstream_host.JetstreamHost, sonic_host.SonicHost,
               adb_host.ADBHost,]
 OS_HOST_DICT = {'android': adb_host.ADBHost,
                 'brillo': adb_host.ADBHost,
diff --git a/server/hosts/jetstream_host.py b/server/hosts/jetstream_host.py
index e8d415d..533fba0 100644
--- a/server/hosts/jetstream_host.py
+++ b/server/hosts/jetstream_host.py
@@ -7,22 +7,13 @@
 Host customization provided for fine-tuning autotest reset, verification,
 and provisioning on Jetstream devices. A more customized host wrapper is
 typicaly used in Jetstream autotests.
-
-This host is not currently probed for in the create_host autodetection logic.
-
-To use this host, the |os_type| host attribute must be set:
-
-  os_type: jetstream
-
-Otherwise, CrosHost will be used for Jetstream devices.
-
-TODO(lgoodby): when known stable, plug this host into the autodection logic.
 """
 
 import logging
 
 import common
 from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import lsbrelease_utils
 from autotest_lib.server.hosts import cros_host
 from autotest_lib.server.hosts import cros_repair
 
@@ -37,6 +28,24 @@
 class JetstreamHost(cros_host.CrosHost):
     """Jetstream-specific host class."""
 
+    @staticmethod
+    def check_host(host, timeout=10):
+        """
+        Check if the given host is jetstream host.
+
+        @param host: An ssh host representing a device.
+        @param timeout: The timeout for the run command.
+
+        @return: True if the host is a Jetstream device, otherwise False.
+        """
+        try:
+            lsb_release_content = host.run(
+                'grep CHROMEOS_RELEASE_BOARD /etc/lsb-release').stdout
+            return lsbrelease_utils.is_jetstream(
+                lsb_release_content=lsb_release_content)
+        except (error.AutoservRunError, error.AutoservSSHTimeout):
+            return False
+
     def _initialize(self, *args, **dargs):
         logging.debug('Initializing Jetstream host')
         super(JetstreamHost, self)._initialize(*args, **dargs)