afdo: Implement kernel afdo verification builders.

As a part of PUpr project, we need to move the publish of new
kernel profiles out of PFQ. This patch implements the builder that
performs the publish. More importantly, this builder adds a new
functionality that old PFQ doesn't have: we will take the latest
kernel profile, try to patch the ebuild, and build kernel to see
if there's any problem with profile, before publishing it.

The patch mainly generalized the functions for
orderfile-verify-toolchain builder to support both orderfile and
kernel AFDO profiles. And also uses new build API for future
transitions.

BUG=chromium:990855,chromium:984153
TEST=https://ci.chromium.org/p/chromeos/builders/general/Try/b8903406507089410256

Change-Id: I5695c4258420ead180943d22a8d1bfa207a3095e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1763922
Tested-by: Tiancong Wang <tcwang@google.com>
Commit-Queue: Tiancong Wang <tcwang@google.com>
Reviewed-by: Evan Hernandez <evanhernandez@chromium.org>
diff --git a/api/controller/toolchain_unittest.py b/api/controller/toolchain_unittest.py
index f46afd1..7d66651 100644
--- a/api/controller/toolchain_unittest.py
+++ b/api/controller/toolchain_unittest.py
@@ -7,95 +7,123 @@
 
 from __future__ import print_function
 
-import os
-
-import mock
-
 from chromite.api import api_config
 from chromite.api.controller import toolchain
 from chromite.api.gen.chromite.api import toolchain_pb2
 
+from chromite.lib import cros_build_lib
 from chromite.lib import cros_test_lib
-from chromite.lib import gs
 from chromite.lib import toolchain_util
 
 
-class UpdateChromeEbuildWithOrderfileTest(cros_test_lib.MockTestCase,
-                                          api_config.ApiConfigMixin):
-  """Unittests for UpdateChromeEbuildWithOrderfile."""
+class UpdateEbuildWithAFDOArtifactsTest(cros_test_lib.MockTestCase,
+                                        api_config.ApiConfigMixin):
+  """Unittests for UpdateEbuildWithAFDOArtifacts."""
+
+  @staticmethod
+  def mock_die(message, *args):
+    raise cros_build_lib.DieSystemExit(message % args)
 
   def setUp(self):
-    self.command = self.PatchObject(toolchain_util,
-                                    'OrderfileUpdateChromeEbuild')
     self.board = 'board'
-    self.input_proto = toolchain_pb2.UpdateChromeEbuildRequest(
-        build_target={'name': self.board})
+    self.response = toolchain_pb2.VerifyAFDOArtifactsResponse()
+    self.invalid_artifact_type = toolchain_pb2.BENCHMARK_AFDO
+    self.orderfile_command = self.PatchObject(
+        toolchain_util, 'OrderfileUpdateChromeEbuild', return_value=True)
+    self.kernel_command = self.PatchObject(
+        toolchain_util, 'AFDOUpdateKernelEbuild', return_value=True)
+    self.PatchObject(cros_build_lib, 'Die', new=self.mock_die)
+
+  def _GetRequest(self, build_target=None, artifact_type=None):
+    return toolchain_pb2.VerifyAFDOArtifactsRequest(
+        build_target={'name': build_target},
+        artifact_type=artifact_type,
+    )
 
   def testValidateOnly(self):
     """Sanity check that a validate only call does not execute any logic."""
     patch = self.PatchObject(toolchain_util, 'OrderfileUpdateChromeEbuild')
-
-    response = toolchain_pb2.UpdateChromeEbuildResponse()
-    toolchain.UpdateChromeEbuildWithOrderfile(self.input_proto, response,
-                                              self.validate_only_config)
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.ORDERFILE)
+    toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                            self.validate_only_config)
     patch.assert_not_called()
 
-  def testSuccess(self):
-    """Test the command is called correctly."""
-    output_proto = toolchain_pb2.UpdateChromeEbuildResponse()
-    toolchain.UpdateChromeEbuildWithOrderfile(self.input_proto,
-                                              output_proto, self.api_config)
-    self.command.assert_called_once_with(self.board)
+  def testWrongArtifactType(self):
+    """Test passing wrong artifact type."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=self.invalid_artifact_type)
+    with self.assertRaises(cros_build_lib.DieSystemExit) as context:
+      toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                              self.api_config)
+    self.assertIn('artifact_type (%d) must be in' % self.invalid_artifact_type,
+                  str(context.exception))
+
+  def testOrderfileSuccess(self):
+    """Test the command is called correctly with orderfile."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.ORDERFILE)
+    toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                            self.api_config)
+    self.orderfile_command.assert_called_once_with(self.board)
+    self.kernel_command.assert_not_called()
+
+  def testKernelAFDOSuccess(self):
+    """Test the command is called correctly with kernel afdo."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.KERNEL_AFDO)
+    toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                            self.api_config)
+    self.kernel_command.assert_called_once_with(self.board)
+    self.orderfile_command.assert_not_called()
 
 
-class UploadVettedOrderfileTest(cros_test_lib.MockTestCase,
-                                api_config.ApiConfigMixin):
-  """Unittests for UploadVettedOrderfile."""
+class UploadVettedFDOArtifactsTest(
+    UpdateEbuildWithAFDOArtifactsTest):
+  """Unittests for UploadVettedAFDOArtifacts."""
+
+  @staticmethod
+  def mock_die(message, *args):
+    raise cros_build_lib.DieSystemExit(message % args)
 
   def setUp(self):
-    self.find_command = self.PatchObject(
+    self.board = 'board'
+    self.response = toolchain_pb2.VerifyAFDOArtifactsResponse()
+    self.invalid_artifact_type = toolchain_pb2.BENCHMARK_AFDO
+    self.command = self.PatchObject(
         toolchain_util,
-        'FindLatestChromeOrderfile',
-        return_value='orderfile-3.0.tar.xz')
-    self.copy_command = self.PatchObject(gs.GSContext, 'Copy', autospec=True)
-    self.from_url = os.path.join(toolchain_util.ORDERFILE_GS_URL_UNVETTED,
-                                 'orderfile-3.0.tar.xz')
-    self.to_url = os.path.join(toolchain_util.ORDERFILE_GS_URL_VETTED,
-                               'orderfile-3.0.tar.xz')
-    self.input_proto = toolchain_pb2.UploadVettedOrderfileRequest()
+        'UploadAndPublishVettedAFDOArtifacts',
+        return_value=True)
+    self.PatchObject(cros_build_lib, 'Die', new=self.mock_die)
 
   def testValidateOnly(self):
     """Sanity check that a validate only call does not execute any logic."""
-    patch = self.PatchObject(toolchain_util, 'OrderfileUpdateChromeEbuild')
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.ORDERFILE)
+    toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                            self.validate_only_config)
+    self.command.assert_not_called()
 
-    response = toolchain_pb2.UploadVettedOrderfileResponse()
-    toolchain.UploadVettedOrderfile(self.input_proto, response,
-                                    self.validate_only_config)
-    patch.assert_not_called()
+  def testWrongArtifactType(self):
+    """Test passing wrong artifact type."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=self.invalid_artifact_type)
+    with self.assertRaises(cros_build_lib.DieSystemExit) as context:
+      toolchain.UpdateEbuildWithAFDOArtifacts(request, self.response,
+                                              self.api_config)
+    self.assertIn('artifact_type (%d) must be in' % self.invalid_artifact_type,
+                  str(context.exception))
 
-  def testRunFail(self):
-    """Test that it fails to upload an orderfile because it's already vetted."""
-    exist_command = self.PatchObject(gs.GSContext, 'Exists', return_value=True)
-    output_proto = toolchain_pb2.UploadVettedOrderfileResponse()
-    toolchain.UploadVettedOrderfile(self.input_proto, output_proto,
-                                    self.api_config)
-    self.assertFalse(output_proto.status)
-    self.find_command.assert_called_once_with(
-        toolchain_util.ORDERFILE_GS_URL_UNVETTED)
-    exist_command.assert_called_once_with(self.to_url)
+  def testOrderfileSuccess(self):
+    """Test the command is called correctly with orderfile."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.ORDERFILE)
+    toolchain.UploadVettedAFDOArtifacts(request, self.response, self.api_config)
+    self.command.assert_called_once_with('orderfile', self.board)
 
-  def testRunPass(self):
-    """Test run successfully."""
-    exist_command = self.PatchObject(gs.GSContext, 'Exists', return_value=False)
-    output_proto = toolchain_pb2.UploadVettedOrderfileResponse()
-    toolchain.UploadVettedOrderfile(self.input_proto, output_proto,
-                                    self.api_config)
-    self.assertTrue(output_proto.status)
-    self.find_command.assert_called_once_with(
-        toolchain_util.ORDERFILE_GS_URL_UNVETTED)
-    exist_command.assert_called_once_with(self.to_url)
-    self.copy_command.assert_called_once_with(
-        mock.ANY,  # Placeholder for 'self'
-        self.from_url,
-        self.to_url,
-        acl='public-read')
+  def testKernelAFDOSuccess(self):
+    """Test the command is called correctly with kernel afdo."""
+    request = self._GetRequest(
+        build_target=self.board, artifact_type=toolchain_pb2.KERNEL_AFDO)
+    toolchain.UploadVettedAFDOArtifacts(request, self.response, self.api_config)
+    self.command.assert_called_once_with('kernel_afdo', self.board)