[autotest] Don't schedule HWTests if less than 4 duts in the pool is available.

BUG=chromium:372085
TEST=unittest, test run_suite against different boards/pools in cautotest
Confirm run_suite returns warning if not enough duts available.

CQ-DEPEND=CL:198802

Change-Id: Icc2dd0adf2d9bb62da61e132ed04229d4f2bdc01
Reviewed-on: https://chromium-review.googlesource.com/201078
Commit-Queue: Dan Shi <[email protected]>
Tested-by: Dan Shi <[email protected]>
Reviewed-by: Prashanth B <[email protected]>
diff --git a/site_utils/diagnosis_utils.py b/site_utils/diagnosis_utils.py
index a46fd78..7367c07 100644
--- a/site_utils/diagnosis_utils.py
+++ b/site_utils/diagnosis_utils.py
@@ -10,8 +10,13 @@
 
 import common
 
+from autotest_lib.client.common_lib import global_config
+from autotest_lib.server import utils
 from autotest_lib.server.cros.dynamic_suite import reporting_utils
 
+# Minimum number of duts to allow a suite job being queued.
+MIN_AVAILABLE_DUTS = global_config.global_config.get_config_value(
+        'SERVER', 'minimum_available_duts', type=int, default=4)
 
 class JobTimer(object):
     """Utility class capable of measuring job timeouts.
@@ -125,6 +130,39 @@
                           limit, time_delta_hours, job_info)
 
 
+    def check_dut_availability(self, board, pool):
+        """Check if DUT availability for a given board and pool is less than
+        minimum.
+
+        @param board: The board to check DUT availability.
+        @param pool: The pool to check DUT availability.
+        @raise: TestLabException if DUT availability is lower than minimum,
+                or failed to get host information from rpc interface.
+        """
+        hosts = self.rpc_interface.get_hosts(
+                invalid=False,
+                multiple_labels=('pool:%s' % pool, 'board:%s' % board))
+        if not hosts:
+            raise utils.TestLabException(
+                    'Unable to retrieve hosts in given board %s pool %s with '
+                    'the rpc_interface %s' % (board, pool, self.rpc_interface))
+
+        # TODO(dshi): Replace the hard coded string with enum value,
+        # models.Host.Status.REPAIRING and REPAIR_FAILED
+        # setup_django_environment can't be imported now as paygen server does
+        # not have django package.
+        bad_statuses = ('Repair Failed', 'Repairing')
+        available_hosts = [host for host in hosts
+                           if not host.status in bad_statuses]
+        logging.debug('%d of %d DUTs are available for board %s pool %s.',
+                      len(available_hosts), len(hosts), board, pool)
+        if len(available_hosts) < MIN_AVAILABLE_DUTS:
+            raise utils.TestLabException(
+                    'Number of available DUTs for board %s pool %s is %d, which'
+                    ' is less than the minimum value %d.' %
+                    (board, pool, len(available_hosts), MIN_AVAILABLE_DUTS))
+
+
     def diagnose_job(self, job_id):
         """Diagnose a suite job.
 
@@ -145,5 +183,3 @@
                              reporting_utils.link_job(job.id))
         else:
             logging.info('All jobs in suite have already completed.')
-
-