First iteration of pluggable metahost handlers.  This change adds the basic framework and moves the default, label-based metahost assignment code into a handler.  It includes some refactorings to the basic scheduling code to make things a bit cleaner.

Signed-off-by: Steve Howard <showard@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@4232 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/scheduler/metahost_scheduler.py b/scheduler/metahost_scheduler.py
new file mode 100644
index 0000000..9588e95
--- /dev/null
+++ b/scheduler/metahost_scheduler.py
@@ -0,0 +1,108 @@
+from autotest_lib.client.common_lib import utils
+
+class HostSchedulingUtility(object):
+    """Interface to host availability information from the scheduler."""
+    def hosts_in_label(self, label_id):
+        """Return potentially usable hosts with the given label."""
+        raise NotImplementedError
+
+
+    def remove_host_from_label(self, host_id, label_id):
+        """Remove this host from the internal list of usable hosts in the label.
+
+        This is provided as an optimization -- when code gets a host from a
+        label and concludes it's unusable, it can call this to avoid getting
+        that host again in the future (within this tick).  This function should
+        not affect correctness.
+        """
+        raise NotImplementedError
+
+
+    def pop_host(self, host_id):
+        """Remove and return a host from the internal list of available hosts.
+        """
+        raise NotImplementedError
+
+
+    def ineligible_hosts_for_entry(self, queue_entry):
+        """Get the list of hosts ineligible to run the given queue entry."""
+        raise NotImplementedError
+
+
+    def is_host_usable(self, host_id):
+        """Determine if the host is currently usable at all."""
+        raise NotImplementedError
+
+
+    def is_host_eligible_for_job(self, host_id, queue_entry):
+        """Determine if the host is eligible specifically for this queue entry.
+
+        @param queue_entry: a HostQueueEntry DBObject
+        """
+        raise NotImplementedError
+
+
+class MetahostScheduler(object):
+    def can_schedule_metahost(self, queue_entry):
+        """Return true if this object can schedule the given queue entry.
+
+        At most one MetahostScheduler should return true for any given entry.
+
+        @param queue_entry: a HostQueueEntry DBObject
+        """
+        raise NotImplementedError
+
+
+    def schedule_metahost(self, queue_entry, scheduling_utility):
+         """Schedule the given queue entry, if possible.
+
+         This method should make necessary database changes culminating in
+         assigning a host to the given queue entry in the database.  It may
+         take no action if no host can be assigned currently.
+
+         @param queue_entry: a HostQueueEntry DBObject
+         @param scheduling_utility: a HostSchedulingUtility object
+         """
+         raise NotImplementedError
+
+
+    def recovery_on_startup(self):
+        """Perform any necessary recovery upon scheduler startup."""
+        pass
+
+
+    def tick(self):
+        """Called once per scheduler cycle; any actions are allowed."""
+        pass
+
+
+class LabelMetahostScheduler(MetahostScheduler):
+    def can_schedule_metahost(self, queue_entry):
+        return bool(queue_entry.meta_host)
+
+
+    def schedule_metahost(self, queue_entry, scheduling_utility):
+        label_id = queue_entry.meta_host
+        hosts_in_label = scheduling_utility.hosts_in_label(label_id)
+        ineligible_host_ids = scheduling_utility.ineligible_hosts_for_entry(
+                queue_entry)
+
+        for host_id in hosts_in_label:
+            if not scheduling_utility.is_host_usable(host_id):
+                scheduling_utility.remove_host_from_label(host_id, label_id)
+                continue
+            if host_id in ineligible_host_ids:
+                continue
+            if not scheduling_utility.is_host_eligible_for_job(host_id,
+                                                               queue_entry):
+                continue
+
+            # Remove the host from our cached internal state before returning
+            scheduling_utility.remove_host_from_label(host_id, label_id)
+            host = scheduling_utility.pop_host(host_id)
+            queue_entry.set_host(host)
+            return
+
+
+def get_metahost_schedulers():
+    return [LabelMetahostScheduler()]