two new major features:

(1) added test and job dependencies
 -added M2M relationship between tests and labels and between jobs and labels, for tracking the labels on which a test/job depends
 -modified test_importer to read the DEPENDENCIES field and create the right M2M relationships
 -modified generate_control_file() RPC to compute and return the union of test dependencies.  since generate_control_file now returns four pieces of information, i converted its return type from tuple to dict, and changed clients accordingly.
 -modified job creation clients (GWT and CLI) to pass this dependency list to the create_job() RPC
 -modified the create_job() RPC to check that hosts satisfy job dependencies, and to create M2M relationships
 -modified the scheduler to check dependencies when scheduling jobs
 -modified JobDetailView to show a job's dependencies

(2) added "only_if_needed" bit to labels; if true, a machine with this label can only be used if the label is requested (either by job dependencies or by the metahost label)
 -added boolean field to Labels
 -modified CLI label creation/viewing to support this new field
 -made create_job() RPC and scheduler check for hosts with such a label that was not requested, and reject such hosts

also did some slight refactoring of other code in create_job() to simplify it while I was changing things there.

a couple notes:
-an only_if_needed label can be used if either the job depends on the label or it's a metahost for that label.  we assume that if the user specifically requests the label in a metahost, then it's OK, even if the job doesn't depend on that label.
-one-time-hosts are assumed to satisfy job dependencies.

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


git-svn-id: http://test.kernel.org/svn/autotest/trunk@2215 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/afe/rpc_utils.py b/frontend/afe/rpc_utils.py
index ac175f0..fcdc0ba 100644
--- a/frontend/afe/rpc_utils.py
+++ b/frontend/afe/rpc_utils.py
@@ -142,4 +142,44 @@
     if label:
         label = models.Label.smart_get(label)
 
-    return is_server, is_synchronous, test_objects, profiler_objects, label
+    dependencies = set(label.name for label
+                       in models.Label.objects.filter(test__in=test_objects))
+
+    return (dict(is_server=is_server, is_synchronous=is_synchronous,
+                 dependencies=list(dependencies)),
+            test_objects, profiler_objects, label)
+
+
+def check_job_dependencies(host_objects, job_dependencies):
+    """
+    Check that a set of machines satisfies a job's dependencies.
+    host_objects: list of models.Host objects
+    job_dependencies: list of names of labels
+    """
+    # check that hosts satisfy dependencies
+    host_ids = [host.id for host in host_objects]
+    hosts_in_job = models.Host.objects.filter(id__in=host_ids)
+    ok_hosts = hosts_in_job
+    for index, dependency in enumerate(job_dependencies):
+        ok_hosts &= models.Host.objects.filter_custom_join(
+            '_label%d' % index, labels__name=dependency)
+    failing_hosts = (set(host.hostname for host in host_objects) -
+                     set(host.hostname for host in ok_hosts))
+    if failing_hosts:
+        raise model_logic.ValidationError(
+            {'hosts' : 'Host(s) failed to meet job dependencies: ' +
+                       ', '.join(failing_hosts)})
+
+    # check for hosts that have only_if_needed labels that aren't requested
+    labels_not_requested = models.Label.objects.filter(only_if_needed=True,
+                                                       host__id__in=host_ids)
+    labels_not_requested = labels_not_requested.exclude(
+        name__in=job_dependencies)
+    errors = []
+    for label in labels_not_requested:
+        hosts_in_label = hosts_in_job.filter(labels=label)
+        errors.append('Cannot use hosts with label "%s" unless requested: %s' %
+                      (label.name,
+                       ', '.join(host.hostname for host in hosts_in_label)))
+    if errors:
+        raise model_logic.ValidationError({'hosts' : '\n'.join(errors)})