git-cl: Use bb to schedule try jobs.

Bug: 976104
Change-Id: I3423667f1ed9edfc5fa17842932de7704951fc62
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1801799
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
Reviewed-by: Andrii Shyshkalov <tandrii@google.com>
diff --git a/git_cl.py b/git_cl.py
index 91ef86c..52b5147 100755
--- a/git_cl.py
+++ b/git_cl.py
@@ -416,78 +416,74 @@
     buckets: A nested dict mapping bucket names to builders to tests.
     options: Command-line options.
   """
-  assert changelist.GetIssue(), 'CL must be uploaded first'
-  codereview_url = changelist.GetCodereviewServer()
-  assert codereview_url, 'CL must be uploaded first'
-  patchset = patchset or changelist.GetMostRecentPatchset()
-  assert patchset, 'CL must be uploaded first'
+  _, ret_code = subprocess2.communicate(
+      ['bb', 'auth-info'], stdout=subprocess2.VOID, stderr=subprocess2.VOID)
+  if ret_code:
+    DieWithError('"bb auth-login" must be executed before scheduling try jobs.')
 
-  codereview_host = urlparse.urlparse(codereview_url).hostname
-  # Cache the buildbucket credentials under the codereview host key, so that
-  # users can use different credentials for different buckets.
-  authenticator = auth.get_authenticator_for_host(codereview_host, auth_config)
-  http = authenticator.authorize(httplib2.Http())
-  http.force_exception_to_status_code = True
-
-  buildbucket_put_url = (
-      'https://{hostname}/_ah/api/buildbucket/v1/builds/batch'.format(
-          hostname=options.buildbucket_host))
-  buildset = 'patch/gerrit/{hostname}/{issue}/{patch}'.format(
-      hostname=codereview_host,
-      issue=changelist.GetIssue(),
-      patch=patchset)
-
-  shared_parameters_properties = changelist.GetTryJobProperties(patchset)
-  shared_parameters_properties['category'] = options.category
+  gerrit_change = changelist.GetGerritChange()
+  shared_properties = {
+      'category': options.category,
+  }
   if options.clobber:
-    shared_parameters_properties['clobber'] = True
+    shared_properties['clobber'] = True
   extra_properties = _get_properties_from_options(options)
   if extra_properties:
-    shared_parameters_properties.update(extra_properties)
+    shared_properties.update(extra_properties)
 
-  batch_req_body = {'builds': []}
+  batch_request = {'requests': []}
   print_text = []
   print_text.append('Tried jobs on:')
   for bucket, builders_and_tests in sorted(buckets.iteritems()):
     print_text.append('Bucket: %s' % bucket)
+    project, bucket = bucket.split('.', 1)
     for builder, tests in sorted(builders_and_tests.iteritems()):
       print_text.append('  %s: %s' % (builder, tests))
-      parameters = {
-          'builder_name': builder,
-          'changes': [{
-              'author': {'email': changelist.GetIssueOwner()},
-              'revision': options.revision,
-          }],
-          'properties': shared_parameters_properties.copy(),
-      }
+      properties = shared_properties.copy()
       if 'presubmit' in builder.lower():
-        parameters['properties']['dry_run'] = 'true'
+        properties['dry_run'] = 'true'
       if tests:
-        parameters['properties']['testfilter'] = tests
+        properties['testfilter'] = tests
 
       tags = [
-          'builder:%s' % builder,
-          'buildset:%s' % buildset,
-          'user_agent:git_cl_try',
+          {
+              'key': 'builder',
+              'value': builder,
+          },
+          {
+              'key': 'user_agent',
+              'value': 'git_cl_try',
+          },
       ]
 
-      batch_req_body['builds'].append(
+      batch_request['requests'].append(
           {
-              'bucket': bucket,
-              'parameters_json': json.dumps(parameters),
-              'client_operation_id': str(uuid.uuid4()),
-              'tags': tags,
+              'scheduleBuild': {
+                  'builder': {
+                      'project': options.project or project,
+                      'bucket': bucket,
+                      'builder': builder,
+                  },
+                  'properties': properties,
+                  'requestId': str(uuid.uuid4()),
+                  'gerritChanges': [gerrit_change],
+                  'tags': tags,
+              }
           }
       )
 
-  _buildbucket_retry(
-      'triggering tryjobs',
-      http,
-      buildbucket_put_url,
-      'PUT',
-      body=json.dumps(batch_req_body),
-      headers={'Content-Type': 'application/json'}
-  )
+  (stdout, _), ret_code = subprocess2.communicate(
+      ['bb', 'batch', '-host', options.buildbucket_host],
+      stdin=json.dumps(batch_request, sort_keys=True),
+      stdout=subprocess2.PIPE)
+  if ret_code:
+    print_text.append('Failed to schedule builds for some bots:')
+    if stdout:
+      responses = json.loads(stdout)
+    print_text.extend(
+        '  ' + response['error']['message']
+        for response in responses['responses'] if 'error' in response)
+
   print_text.append('To see results here, run:        git cl try-results')
   print_text.append('To see results in browser, run:  git cl web')
   print('\n'.join(print_text))
@@ -1617,10 +1613,6 @@
     """Returns reason (str) if unable trigger tryjobs on this CL or None."""
     return self._codereview_impl.CannotTriggerTryJobReason()
 
-  def GetTryJobProperties(self, patchset=None):
-    """Returns dictionary of properties to launch tryjob."""
-    return self._codereview_impl.GetTryJobProperties(patchset=patchset)
-
   def __getattr__(self, attr):
     # This is because lots of untested code accesses Rietveld-specific stuff
     # directly, and it's hard to fix for sure. So, just let it work, and fix
@@ -1760,10 +1752,6 @@
   def GetReviewers(self):
     raise NotImplementedError()
 
-  def GetTryJobProperties(self, patchset=None):
-    raise NotImplementedError()
-
-
 class _GerritChangelistImpl(_ChangelistCodereviewBase):
   def __init__(self, changelist, codereview_host=None):
     super(_GerritChangelistImpl, self).__init__(changelist)
@@ -2882,26 +2870,27 @@
     if data['status'] in ('ABANDONED', 'MERGED'):
       return 'CL %s is closed' % self.GetIssue()
 
-  def GetTryJobProperties(self, patchset=None):
-    """Returns dictionary of properties to launch a tryjob."""
-    data = self._GetChangeDetail(['ALL_REVISIONS'])
+  def GetGerritChange(self, patchset=None):
+    """Returns a buildbucket.v2.GerritChange message for the current issue."""
+    host = urlparse.urlparse(self.GetCodereviewServer()).hostname
+    issue = self.GetIssue()
     patchset = int(patchset or self.GetPatchset())
-    assert patchset
-    revision_data = None  # Pylint wants it to be defined.
-    for revision_data in data['revisions'].itervalues():
-      if int(revision_data['_number']) == patchset:
-        break
-    else:
+    data = self._GetChangeDetail(['ALL_REVISIONS'])
+
+    assert host and issue and patchset, 'CL must be uploaded first'
+
+    has_patchset = any(
+        int(revision_data['_number']) == patchset
+        for revision_data in data['revisions'].itervalues())
+    if not has_patchset:
       raise Exception('Patchset %d is not known in Gerrit change %d' %
                       (patchset, self.GetIssue()))
+
     return {
-      'patch_issue': self.GetIssue(),
-      'patch_set': patchset or self.GetPatchset(),
-      'patch_project': data['project'],
-      'patch_storage': 'gerrit',
-      'patch_ref': revision_data['fetch']['http']['ref'],
-      'patch_repository_url': revision_data['fetch']['http']['url'],
-      'patch_gerrit_url': self.GetCodereviewServer(),
+        'host': host,
+        'change': issue,
+        'project': data['project'],
+        'patchset': patchset,
     }
 
   def GetIssueOwner(self):