Include slave build statuses in master build metadata.json

This CL adds information about slave builds to the metadata.json
file of a CQ-master build. These are added as an entry of the form:

{
...
  'slave_targets' : {
    'x86-mario-paladin' : {
      status : 'fail',
      message : 'string-formatted message'
    },
    'lumpy-paladin' : {
      ...
    },
    ...
  }
...
}

Adding this slave metadata required a way for the ReportStage to reference
to CommitQueueCompletion stage, in order to pull in the slave statuses,
and a way to flatten a BuilderStatus object into a json-encodable string-
to-string dictionary.

BUG=chromium:287880
TEST=Ran a number of local `cbuildbot --local --buildbot --debug` runs,
and examined the dropped metadata.json file, which contained a
'slave_targets' key. In my local tests, I only saw slave results for
the build configuration that I kicked off; in prod this should also
include the other CQ build slaves. I'm convinced the easiest way to test
this is to just put this change in.

Change-Id: I5aeb5d9d470e815ae7e7e7fbf75bad7a2f286cf9
Reviewed-on: https://chromium-review.googlesource.com/172140
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
Tested-by: Aviv Keshet <akeshet@chromium.org>
Reviewed-by: Matt Tennant <mtennant@chromium.org>
Commit-Queue: David James <davidjames@chromium.org>
diff --git a/scripts/cbuildbot.py b/scripts/cbuildbot.py
index 9115bf5..9cd0b4a 100644
--- a/scripts/cbuildbot.py
+++ b/scripts/cbuildbot.py
@@ -203,6 +203,15 @@
     """
     raise NotImplementedError()
 
+  def GetCompletionInstance(self):
+    """Returns the LKGMCandidateSyncCompletionStage for this build.
+
+    Subclasses may override this method.
+
+    Returns: None
+    """
+    return None
+
   def RunStages(self):
     """Subclasses must override this method.  Runs the appropriate code."""
     raise NotImplementedError()
@@ -342,8 +351,9 @@
     finally:
       if print_report:
         results_lib.WriteCheckpoint(self.options.buildroot)
+        completion_instance = self.GetCompletionInstance()
         self._RunStage(stages.ReportStage, self.archive_stages,
-                       self.release_tag, sync_instance)
+                       self.release_tag, sync_instance, completion_instance)
         success = results_lib.Results.BuildSucceededSoFar()
         if exception_thrown and success:
           success = False
@@ -546,6 +556,7 @@
     super(DistributedBuilder, self).__init__(*args, **kwargs)
     self.completion_stage_class = None
     self.sync_stage = None
+    self._completion_stage = None
 
   def GetSyncInstance(self):
     """Syncs the tree using one of the distributed sync logic paths.
@@ -572,11 +583,20 @@
     self.sync_stage = sync_stage
     return self.sync_stage
 
+  def GetCompletionInstance(self):
+    """Returns the completion_stage_class instance that was used for this build.
+
+    Returns None if the completion_stage instance was not yet created (this
+    occurs during Publish).
+    """
+    return self._completion_stage
+
   def Publish(self, was_build_successful):
     """Completes build by publishing any required information."""
     completion_stage = self._GetStageInstance(self.completion_stage_class,
                                               self.sync_stage,
                                               was_build_successful)
+    self._completion_stage = completion_stage
     completion_stage.Run()
     name = completion_stage.name
     if (self.build_config['pre_cq'] or self.options.pre_cq or