[autotest] Allow weekly suite to be scheduled at any day of a week.

This change is similar to CL 323331. It enables suite scheduler to run weekly
task at a given day of the week. This allows the weekly suites to be distributed
across the week, rather than all run at once, which causes a spike of load on
devservers and lab network.

BUG=chromium:245937
TEST=unittest, suite_scheduler.py --sanity

Change-Id: I20cdc8abae30a552aed6d3f00a2b2e283a287a52
Reviewed-on: https://chromium-review.googlesource.com/323805
Commit-Ready: Dan Shi <dshi@google.com>
Tested-by: Dan Shi <dshi@google.com>
Reviewed-by: Paul Hobbs <phobbs@google.com>
Reviewed-by: Fang Deng <fdeng@chromium.org>
diff --git a/site_utils/suite_scheduler/task.py b/site_utils/suite_scheduler/task.py
index 2829204..cfbe789 100644
--- a/site_utils/suite_scheduler/task.py
+++ b/site_utils/suite_scheduler/task.py
@@ -158,7 +158,7 @@
         allowed = set(['suite', 'run_on', 'branch_specs', 'pool', 'num',
                        'boards', 'file_bugs', 'cros_build_spec',
                        'firmware_rw_build_spec', 'test_source', 'job_retry',
-                       'hour'])
+                       'hour', 'day'])
         # The parameter of union() is the keys under the section in the config
         # The union merges this with the allowed set, so if any optional keys
         # are omitted, then they're filled in. If any extra keys are present,
@@ -208,6 +208,20 @@
             raise MalformedConfigEntry(
                     '`hour` is the trigger time that can only apply to nightly '
                     'event.')
+
+        try:
+            day = config.getint(section, 'day')
+        except ValueError as e:
+            raise MalformedConfigEntry("Ill-specified 'day': %r" % e)
+        if day is not None and (day < 0 or day > 6):
+            raise MalformedConfigEntry(
+                    '`day` must be an integer between 0 and 6, where 0 is for '
+                    'Monday and 6 is for Sunday.')
+        if day is not None and keyword != 'weekly':
+            raise MalformedConfigEntry(
+                    '`day` is the trigger of the day of a week, that can only '
+                    'apply to weekly events.')
+
         specs = []
         if branches:
             specs = re.split('\s*,\s*', branches)
@@ -218,7 +232,7 @@
                              cros_build_spec=cros_build_spec,
                              firmware_rw_build_spec=firmware_rw_build_spec,
                              test_source=test_source, job_retry=job_retry,
-                             hour=hour)
+                             hour=hour, day=day)
 
 
     @staticmethod
@@ -251,7 +265,7 @@
     def __init__(self, name, suite, branch_specs, pool=None, num=None,
                  boards=None, priority=None, timeout=None, file_bugs=False,
                  cros_build_spec=None, firmware_rw_build_spec=None,
-                 test_source=None, job_retry=False, hour=None):
+                 test_source=None, job_retry=False, hour=None, day=None):
         """Constructor
 
         Given an iterable in |branch_specs|, pre-vetted using CheckBranchSpecs,
@@ -327,6 +341,8 @@
                           False.
         @param hour: An integer specifying the hour that a nightly run should
                      be triggered, default is set to 21.
+        @param day: An integer specifying the day of a week that a weekly run
+                    should be triggered, default is set to 5, which is Saturday.
         """
         self._name = name
         self._suite = suite
@@ -341,6 +357,7 @@
         self._test_source = test_source
         self._job_retry = job_retry
         self.hour = hour
+        self.day = day
 
         if ((self._firmware_rw_build_spec or cros_build_spec) and
             not self.test_source in [Builds.FIRMWARE_RW, Builds.CROS]):