autotest: Kick off a provision suite.

BUG=chromium:828662
TEST=Ran 'bin/run_suite_skylab --board=link --suite_name=provision
--build=link-release/R68-10614.0.0 --priority 10 --suite_args
"{u'num_required': 1}" --timeout_mins 5' locally.

Change-Id: I530a8c586c51d229f030d38d2c01bf9b1fe7d134
Reviewed-on: https://chromium-review.googlesource.com/1058183
Reviewed-by: Xixuan Wu <xixuan@chromium.org>
Tested-by: Xixuan Wu <xixuan@chromium.org>
Trybot-Ready: Xixuan Wu <xixuan@chromium.org>
Commit-Queue: Xixuan Wu <xixuan@chromium.org>
diff --git a/venv/skylab_suite/cros_suite.py b/venv/skylab_suite/cros_suite.py
index 9a88fbc..d3788f2 100644
--- a/venv/skylab_suite/cros_suite.py
+++ b/venv/skylab_suite/cros_suite.py
@@ -20,8 +20,10 @@
 from __future__ import print_function
 
 import collections
+import logging
 
 from lucifer import autotest
+from skylab_suite import swarming_lib
 
 
 SuiteSpecs = collections.namedtuple(
@@ -31,6 +33,8 @@
                 'suite_name',
                 'suite_file_name',
                 'test_source_build',
+                'suite_args',
+                'timeout_mins',
         ])
 
 
@@ -38,6 +42,40 @@
   """Raised if a suite's property is not valid."""
 
 
+class RetryHandler(object):
+    """The class for handling retries for a CrOS suite."""
+
+    def __init__(self, provision_num_required=0):
+        self.provision_num_required = provision_num_required
+        self._active_child_tasks = []
+
+
+    def handle_results(self, all_tasks):
+        """Handle child tasks' results."""
+        self._active_child_tasks = all_tasks
+
+
+    def finished_waiting(self):
+        """Check whether the suite should finish its waiting."""
+        if self.provision_num_required > 0:
+            successfully_completed_bots = set()
+            for t in self._active_child_tasks:
+                if (t['state'] == swarming_lib.TASK_COMPLETED and
+                    (not t['failure'])):
+                    successfully_completed_bots.add(t['bot_id'])
+
+            logging.info('Found %d successfully provisioned bots',
+                         len(successfully_completed_bots))
+            return (len(successfully_completed_bots) >
+                    self.provision_num_required)
+
+        finished_tasks = [t for t in self._active_child_tasks if t['state'] in
+                          swarming_lib.TASK_FINISHED_STATUS]
+        logging.info('%d/%d child tasks finished',
+                     len(finished_tasks), len(self._active_child_tasks))
+        return len(finished_tasks) == len(self._active_child_tasks)
+
+
 class Suite(object):
     """The class for a CrOS suite."""
 
@@ -55,6 +93,10 @@
         self.test_source_build = specs.test_source_build
         self.suite_name = specs.suite_name
         self.suite_file_name = specs.suite_file_name
+        self.suite_id = None
+        self.timeout_mins = specs.timeout_mins
+
+        self.retry_handler = RetryHandler()
 
 
     @property
@@ -76,7 +118,7 @@
         """Prepare a suite job for execution."""
         self._stage_suite_artifacts()
         self._parse_suite_args()
-        self._find_child_tests()
+        self._find_tests()
 
 
     def _stage_suite_artifacts(self):
@@ -101,7 +143,7 @@
                 self.test_source_build, self.ds, self.suite_file_name)
 
 
-    def _find_child_tests(self):
+    def _find_tests(self):
         """Fetch the child tests."""
         control_file_getter = autotest.load(
                 'server.cros.dynamic_suite.control_file_getter')
@@ -112,3 +154,29 @@
         tests = suite_common.retrieve_for_suite(
                 cf_getter, self.suite_name)
         self.tests = suite_common.filter_tests(tests)
+
+
+class ProvisionSuite(Suite):
+    """The class for a CrOS provision suite."""
+
+    def __init__(self, specs):
+        super(ProvisionSuite, self).__init__(specs)
+        self._num_required = specs.suite_args['num_required']
+        # TODO (xixuan): Ideally the dynamic_suite service is designed
+        # to be decoupled with any lab (RPC) calls. Here to set maximum
+        # DUT number for provision as 10 first.
+        self._num_max = 2
+        self.retry_handler = RetryHandler(self._num_required)
+
+
+    def _find_tests(self):
+        """Fetch the child tests for provision suite."""
+        control_file_getter = autotest.load(
+                'server.cros.dynamic_suite.control_file_getter')
+        suite_common = autotest.load('server.cros.dynamic_suite.suite_common')
+
+        cf_getter = control_file_getter.DevServerGetter(
+                self.test_source_build, self.ds)
+        dummy_test = suite_common.retrieve_control_data_for_test(
+                cf_getter, 'dummy_Pass')
+        self.tests = [dummy_test] * max(self._num_required, self._num_max)