blob: b0cb69704590f13e3b350344d404a9b99fdfe303 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Alex Klein2966e302019-01-17 13:29:38 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Image service tests."""
6
Alex Klein2966e302019-01-17 13:29:38 -07007import os
Joseph Sussman34c01be2022-06-03 14:04:41 +00008from pathlib import Path
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +00009from typing import List, Optional
Mike Frysinger166fea02021-02-12 05:30:33 -050010from unittest import mock
Alex Klein2966e302019-01-17 13:29:38 -070011
Alex Klein231d2da2019-07-22 16:44:45 -060012from chromite.api import api_config
Alex Klein8cb365a2019-05-15 16:24:53 -060013from chromite.api import controller
14from chromite.api.controller import image as image_controller
Alex Klein7107bdd2019-03-14 17:14:31 -060015from chromite.api.gen.chromite.api import image_pb2
Jack Neus761e1842020-12-01 18:20:11 +000016from chromite.api.gen.chromite.api import sysroot_pb2
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040017from chromite.api.gen.chromiumos import common_pb2
Jack Neusbbf75e02023-10-05 16:01:38 +000018from chromite.api.gen.chromiumos import signing_pb2
Josiah Hounyode113e52022-11-30 06:30:33 +000019from chromite.lib import build_target_lib
20from chromite.lib import chroot_lib
Alex Klein56355682019-02-07 10:36:54 -070021from chromite.lib import constants
Alex Klein4f0eb432019-05-02 13:56:04 -060022from chromite.lib import cros_build_lib
Alex Klein2966e302019-01-17 13:29:38 -070023from chromite.lib import cros_test_lib
Michael Mortensenc83c9952019-08-05 12:15:12 -060024from chromite.lib import image_lib
Alex Klein2966e302019-01-17 13:29:38 -070025from chromite.lib import osutils
Josiah Hounyode113e52022-11-30 06:30:33 +000026from chromite.lib import sysroot_lib
Jack Neus761e1842020-12-01 18:20:11 +000027from chromite.scripts import pushimage
Alex Kleinb7cdbe62019-02-22 11:41:32 -070028from chromite.service import image as image_service
Alex Klein2966e302019-01-17 13:29:38 -070029
30
Alex Klein231d2da2019-07-22 16:44:45 -060031class CreateTest(cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin):
Alex Klein1699fab2022-09-08 08:46:06 -060032 """Create image tests."""
Alex Klein56355682019-02-07 10:36:54 -070033
Alex Klein1699fab2022-09-08 08:46:06 -060034 def setUp(self):
35 self.response = image_pb2.CreateImageResult()
Alex Klein231d2da2019-07-22 16:44:45 -060036
Alex Klein1699fab2022-09-08 08:46:06 -060037 def _GetRequest(
38 self,
39 board=None,
40 types=None,
41 version=None,
42 builder_path=None,
43 disable_rootfs_verification=False,
44 base_is_recovery=False,
45 ):
46 """Helper to build a request instance."""
47 return image_pb2.CreateImageRequest(
48 build_target={"name": board},
49 image_types=types,
50 disable_rootfs_verification=disable_rootfs_verification,
51 version=version,
52 builder_path=builder_path,
53 base_is_recovery=base_is_recovery,
54 )
Alex Klein21b95022019-05-09 14:14:46 -060055
Alex Klein1699fab2022-09-08 08:46:06 -060056 def testValidateOnly(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -070057 """Verify a validate-only call does not execute any logic."""
Alex Klein1699fab2022-09-08 08:46:06 -060058 patch = self.PatchObject(image_service, "Build")
Alex Klein21b95022019-05-09 14:14:46 -060059
Alex Klein1699fab2022-09-08 08:46:06 -060060 request = self._GetRequest(board="board")
61 image_controller.Create(
62 request, self.response, self.validate_only_config
63 )
64 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -060065
Alex Klein1699fab2022-09-08 08:46:06 -060066 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -070067 """Test mock call does not execute any logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -060068 patch = self.PatchObject(image_service, "Build")
Michael Mortensen10146cf2019-11-19 19:59:22 -070069
Alex Klein1699fab2022-09-08 08:46:06 -060070 request = self._GetRequest(board="board")
71 image_controller.Create(request, self.response, self.mock_call_config)
72 patch.assert_not_called()
73 self.assertEqual(self.response.success, True)
Michael Mortensen10146cf2019-11-19 19:59:22 -070074
Alex Klein1699fab2022-09-08 08:46:06 -060075 def testMockError(self):
76 """Test that mock call does not execute any logic, returns error."""
77 patch = self.PatchObject(image_service, "Build")
Michael Mortensen85d38402019-12-12 09:50:29 -070078
Alex Klein1699fab2022-09-08 08:46:06 -060079 request = self._GetRequest(board="board")
80 rc = image_controller.Create(
81 request, self.response, self.mock_error_config
82 )
83 patch.assert_not_called()
84 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Michael Mortensen85d38402019-12-12 09:50:29 -070085
Alex Klein1699fab2022-09-08 08:46:06 -060086 def testNoBoard(self):
87 """Test no board given fails."""
88 request = self._GetRequest()
Alex Klein56355682019-02-07 10:36:54 -070089
Alex Klein1699fab2022-09-08 08:46:06 -060090 # No board should cause it to fail.
91 with self.assertRaises(cros_build_lib.DieSystemExit):
92 image_controller.Create(request, self.response, self.api_config)
Alex Klein56355682019-02-07 10:36:54 -070093
Alex Klein1699fab2022-09-08 08:46:06 -060094 def testNoTypeSpecified(self):
95 """Test the image type default."""
96 request = self._GetRequest(board="board")
Alex Klein21b95022019-05-09 14:14:46 -060097
Alex Klein1699fab2022-09-08 08:46:06 -060098 # Failed result to avoid the success handling logic.
99 result = image_service.BuildResult([constants.IMAGE_TYPE_BASE])
100 result.return_code = 1
101 build_patch = self.PatchObject(
102 image_service, "Build", return_value=result
103 )
Alex Klein56355682019-02-07 10:36:54 -0700104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 image_controller.Create(request, self.response, self.api_config)
106 build_patch.assert_any_call(
107 "board", [constants.IMAGE_TYPE_BASE], config=mock.ANY
108 )
Alex Klein56355682019-02-07 10:36:54 -0700109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 def testSingleTypeSpecified(self):
111 """Test it's properly using a specified type."""
112 request = self._GetRequest(
113 board="board", types=[common_pb2.IMAGE_TYPE_DEV]
114 )
Alex Klein21b95022019-05-09 14:14:46 -0600115
Alex Klein1699fab2022-09-08 08:46:06 -0600116 # Failed result to avoid the success handling logic.
117 result = image_service.BuildResult([constants.IMAGE_TYPE_DEV])
118 result.return_code = 1
119 build_patch = self.PatchObject(
120 image_service, "Build", return_value=result
121 )
Alex Klein21b95022019-05-09 14:14:46 -0600122
Alex Klein1699fab2022-09-08 08:46:06 -0600123 image_controller.Create(request, self.response, self.api_config)
124 build_patch.assert_any_call(
125 "board", [constants.IMAGE_TYPE_DEV], config=mock.ANY
126 )
Alex Klein56355682019-02-07 10:36:54 -0700127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 def testMultipleAndImpliedTypes(self):
129 """Test multiple types and implied type handling."""
130 # The TEST_VM type should force it to build the test image.
131 types = [common_pb2.IMAGE_TYPE_BASE, common_pb2.IMAGE_TYPE_TEST_VM]
132 expected_images = [constants.IMAGE_TYPE_BASE, constants.IMAGE_TYPE_TEST]
Alex Klein21b95022019-05-09 14:14:46 -0600133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 request = self._GetRequest(board="board", types=types)
Alex Klein21b95022019-05-09 14:14:46 -0600135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 # Failed result to avoid the success handling logic.
137 result = image_service.BuildResult(expected_images)
138 result.return_code = 1
139 build_patch = self.PatchObject(
140 image_service, "Build", return_value=result
141 )
Alex Klein21b95022019-05-09 14:14:46 -0600142
Alex Klein1699fab2022-09-08 08:46:06 -0600143 image_controller.Create(request, self.response, self.api_config)
144 build_patch.assert_any_call("board", expected_images, config=mock.ANY)
Alex Klein56355682019-02-07 10:36:54 -0700145
Alex Klein1699fab2022-09-08 08:46:06 -0600146 def testRecoveryImpliedTypes(self):
147 """Test implied type handling of recovery images."""
148 # The TEST_VM type should force it to build the test image.
149 types = [common_pb2.IMAGE_TYPE_RECOVERY]
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700150
Alex Klein1699fab2022-09-08 08:46:06 -0600151 request = self._GetRequest(board="board", types=types)
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700152
Alex Klein1699fab2022-09-08 08:46:06 -0600153 # Failed result to avoid the success handling logic.
154 result = image_service.BuildResult([])
155 result.return_code = 1
156 build_patch = self.PatchObject(
157 image_service, "Build", return_value=result
158 )
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 image_controller.Create(request, self.response, self.api_config)
161 build_patch.assert_any_call(
162 "board", [constants.IMAGE_TYPE_BASE], config=mock.ANY
163 )
George Engelbrecht9f4f8322021-03-08 12:04:17 -0700164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 def testFailedPackageHandling(self):
166 """Test failed packages are populated correctly."""
167 result = image_service.BuildResult([])
168 result.return_code = 1
169 result.failed_packages = ["foo/bar", "cat/pkg"]
170 expected_packages = [("foo", "bar"), ("cat", "pkg")]
171 self.PatchObject(image_service, "Build", return_value=result)
Alex Klein1bcd9882019-03-19 13:25:24 -0600172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 input_proto = self._GetRequest(board="board")
Alex Klein1bcd9882019-03-19 13:25:24 -0600174
Alex Klein1699fab2022-09-08 08:46:06 -0600175 rc = image_controller.Create(
176 input_proto, self.response, self.api_config
177 )
Alex Klein231d2da2019-07-22 16:44:45 -0600178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 self.assertEqual(
180 controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE, rc
181 )
182 for package in self.response.failed_packages:
183 self.assertIn(
184 (package.category, package.package_name), expected_packages
185 )
Alex Klein1bcd9882019-03-19 13:25:24 -0600186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 def testNoPackagesFailureHandling(self):
188 """Test failed packages are populated correctly."""
189 result = image_service.BuildResult([])
190 result.return_code = 1
191 self.PatchObject(image_service, "Build", return_value=result)
Alex Kleinb7cdbe62019-02-22 11:41:32 -0700192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 input_proto = image_pb2.CreateImageRequest()
194 input_proto.build_target.name = "board"
Alex Klein2557b4f2019-07-11 14:34:00 -0600195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 rc = image_controller.Create(
197 input_proto, self.response, self.api_config
198 )
199 self.assertTrue(rc)
200 self.assertNotEqual(
201 controller.RETURN_CODE_UNSUCCESSFUL_RESPONSE_AVAILABLE, rc
202 )
203 self.assertFalse(self.response.failed_packages)
Alex Klein2557b4f2019-07-11 14:34:00 -0600204
Jack Neus70490b52022-09-12 14:41:45 +0000205 def testFactory(self):
206 """Test it's properly building factory."""
207 request = self._GetRequest(
208 board="board",
209 types=[
210 common_pb2.IMAGE_TYPE_FACTORY,
211 common_pb2.IMAGE_TYPE_NETBOOT,
212 ],
213 )
214 factory_path = self.tempdir / "factory-shim"
215 factory_path.touch()
216 result = image_service.BuildResult([constants.IMAGE_TYPE_FACTORY_SHIM])
217 result.add_image(constants.IMAGE_TYPE_FACTORY_SHIM, factory_path)
218 result.return_code = 0
219 build_patch = self.PatchObject(
220 image_service, "Build", return_value=result
221 )
222 netboot_patch = self.PatchObject(image_service, "create_netboot_kernel")
223
224 image_controller.Create(request, self.response, self.api_config)
225 build_patch.assert_any_call(
226 "board", [constants.IMAGE_TYPE_FACTORY_SHIM], config=mock.ANY
227 )
Madeleine Hardtf0a92fc2022-09-19 18:41:12 +0000228 netboot_patch.assert_any_call("board", os.path.dirname(factory_path))
Jack Neus70490b52022-09-12 14:41:45 +0000229
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000230
Josiah Hounyode113e52022-11-30 06:30:33 +0000231class GetArtifactsTest(
232 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
233):
234 """GetArtifacts function tests."""
235
Alex Kleinab87ceb2023-01-24 12:00:51 -0700236 # pylint: disable=line-too-long
Josiah Hounyode113e52022-11-30 06:30:33 +0000237 _artifact_funcs = {
238 common_pb2.ArtifactsByService.Image.ArtifactType.DLC_IMAGE: image_service.copy_dlc_image,
239 common_pb2.ArtifactsByService.Image.ArtifactType.LICENSE_CREDITS: image_service.copy_license_credits,
240 common_pb2.ArtifactsByService.Image.ArtifactType.FACTORY_IMAGE: image_service.create_factory_image_zip,
241 common_pb2.ArtifactsByService.Image.ArtifactType.STRIPPED_PACKAGES: image_service.create_stripped_packages_tar,
Jack Neus91846002022-12-01 23:49:22 +0000242 common_pb2.ArtifactsByService.Image.ArtifactType.IMAGE_SCRIPTS: image_service.create_image_scripts_archive,
Josiah Hounyode113e52022-11-30 06:30:33 +0000243 }
Alex Kleinab87ceb2023-01-24 12:00:51 -0700244 # pylint: enable=line-too-long
Josiah Hounyode113e52022-11-30 06:30:33 +0000245
246 def setUp(self):
247 self._mocks = {}
248 for artifact, func in self._artifact_funcs.items():
249 self._mocks[artifact] = self.PatchObject(
250 image_service, func.__name__
251 )
252 self.chroot = chroot_lib.Chroot(
Brian Norris4f251e42023-03-09 15:53:26 -0800253 path=self.tempdir / "chroot",
254 chrome_root=self.tempdir,
255 out_path=self.tempdir / "out",
Josiah Hounyode113e52022-11-30 06:30:33 +0000256 )
257 board = "chell"
258 sysroot_path = "/build/%s" % board
259 self.sysroot_class = sysroot_lib.Sysroot(sysroot_path)
260 self.build_target = build_target_lib.BuildTarget(board)
261
262 def _InputProto(
263 self,
264 artifact_types=_artifact_funcs.keys(),
265 ):
266 """Helper to build an input proto instance."""
267 return common_pb2.ArtifactsByService.Image(
268 output_artifacts=[
269 common_pb2.ArtifactsByService.Image.ArtifactInfo(
270 artifact_types=artifact_types
271 )
272 ]
273 )
274
275 def testNoArtifacts(self):
276 """Test GetArtifacts with no artifact types."""
277 in_proto = self._InputProto(artifact_types=[])
278 image_controller.GetArtifacts(
279 in_proto, self.chroot, self.sysroot_class, self.build_target, ""
280 )
281
282 for _, patch in self._mocks.items():
283 patch.assert_not_called()
284
285 def testArtifactsSuccess(self):
286 """Test GetArtifacts with all artifact types."""
287 image_controller.GetArtifacts(
288 self._InputProto(),
289 self.chroot,
290 self.sysroot_class,
291 self.build_target,
292 "",
293 )
294
295 for _, patch in self._mocks.items():
296 patch.assert_called_once()
297
298 def testArtifactsException(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700299 """Test with all artifact types when one type throws an exception."""
Josiah Hounyode113e52022-11-30 06:30:33 +0000300
301 self._mocks[
302 common_pb2.ArtifactsByService.Image.ArtifactType.STRIPPED_PACKAGES
303 ].side_effect = Exception("foo bar")
304 generated = image_controller.GetArtifacts(
305 self._InputProto(),
306 self.chroot,
307 self.sysroot_class,
308 self.build_target,
309 "",
310 )
311
312 for _, patch in self._mocks.items():
313 patch.assert_called_once()
314
315 found_artifact = False
316 for data in generated:
317 artifact_type = (
318 common_pb2.ArtifactsByService.Image.ArtifactType.Name(
319 data["type"]
320 )
321 )
322 if artifact_type == "STRIPPED_PACKAGES":
323 found_artifact = True
324 self.assertTrue(data["failed"])
325 self.assertEqual(data["failure_reason"], "foo bar")
326 self.assertTrue(found_artifact)
327
328
Alex Klein1699fab2022-09-08 08:46:06 -0600329class RecoveryImageTest(
330 cros_test_lib.RunCommandTempDirTestCase, api_config.ApiConfigMixin
331):
332 """Recovery image tests."""
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 def setUp(self):
335 self.response = image_pb2.CreateImageResult()
336 self.types = [
337 common_pb2.IMAGE_TYPE_BASE,
338 common_pb2.IMAGE_TYPE_RECOVERY,
339 ]
340 self.build_result = self._CreateMockBuildResult(
341 [common_pb2.IMAGE_TYPE_BASE]
342 )
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 self.PatchObject(
345 image_service,
346 "Build",
347 side_effect=[
348 self.build_result,
349 self._CreateMockBuildResult([common_pb2.IMAGE_TYPE_FACTORY]),
350 ],
351 )
352 self.copy_image_mock = self.PatchObject(
353 image_service,
354 "CopyBaseToRecovery",
355 side_effect=[
356 self._CreateMockBuildResult([common_pb2.IMAGE_TYPE_RECOVERY]),
357 ],
358 )
359 self.recov_image_mock = self.PatchObject(
360 image_service,
361 "BuildRecoveryImage",
362 side_effect=[
363 self._CreateMockBuildResult([common_pb2.IMAGE_TYPE_RECOVERY]),
364 ],
365 )
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 def _GetRequest(
368 self,
369 board=None,
370 types=None,
371 version=None,
372 builder_path=None,
373 disable_rootfs_verification=False,
374 base_is_recovery=False,
375 ):
376 """Helper to build a request instance."""
377 return image_pb2.CreateImageRequest(
378 build_target={"name": board},
379 image_types=types,
380 disable_rootfs_verification=disable_rootfs_verification,
381 version=version,
382 builder_path=builder_path,
383 base_is_recovery=base_is_recovery,
384 )
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000385
Alex Klein1699fab2022-09-08 08:46:06 -0600386 def _CreateMockBuildResult(
387 self, image_types: List[int]
388 ) -> Optional[image_service.BuildResult]:
Jack Rosenthal8d3fc832023-06-16 20:04:37 -0600389 """Helper to create Mock `cros build-image` results.
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000390
Alex Klein1699fab2022-09-08 08:46:06 -0600391 Args:
Alex Kleinab87ceb2023-01-24 12:00:51 -0700392 image_types: A list of image types for which the mock BuildResult
393 has to be created.
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000394
Alex Klein1699fab2022-09-08 08:46:06 -0600395 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600396 A list of mock BuildResult.
Alex Klein1699fab2022-09-08 08:46:06 -0600397 """
398 image_types_names = [
399 image_controller.SUPPORTED_IMAGE_TYPES[x]
400 for x in image_types
401 if image_controller.SUPPORTED_IMAGE_TYPES[x]
402 in constants.IMAGE_TYPE_TO_NAME
403 ]
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 if not image_types_names:
406 if (
407 common_pb2.IMAGE_TYPE_FACTORY in image_types
408 and len(image_types) == 1
409 ):
410 image_types_names.append(constants.IMAGE_TYPE_FACTORY_SHIM)
411 else:
412 return None
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000413
Alex Klein1699fab2022-09-08 08:46:06 -0600414 _build_result = image_service.BuildResult(image_types_names)
415 _build_result.return_code = 0
416 for image_type in image_types_names:
417 test_image = (
418 Path(self.tempdir) / constants.IMAGE_TYPE_TO_NAME[image_type]
419 )
420 test_image.touch()
421 _build_result.add_image(image_type, test_image)
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000422
Alex Klein1699fab2022-09-08 08:46:06 -0600423 return _build_result
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000424
Alex Klein1699fab2022-09-08 08:46:06 -0600425 def testBaseIsRecoveryTrue(self):
426 """Test that cp is called."""
427 input_proto = self._GetRequest(
428 board="board", types=self.types, base_is_recovery=True
429 )
430 image_controller.Create(input_proto, self.response, self.api_config)
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000431
Alex Klein1699fab2022-09-08 08:46:06 -0600432 self.copy_image_mock.assert_called_with(
433 board="board",
434 image_path=self.build_result.images[constants.IMAGE_TYPE_BASE],
435 )
Joseph Sussman34c01be2022-06-03 14:04:41 +0000436
Alex Klein1699fab2022-09-08 08:46:06 -0600437 def testBaseIsRecoveryFalse(self):
438 """Test that mod_image_for_recovery.sh is called."""
439 input_proto = self._GetRequest(
440 board="board", types=self.types, base_is_recovery=False
441 )
442 image_controller.Create(input_proto, self.response, self.api_config)
Ram Chandrasekar53c77cb2022-06-09 22:16:42 +0000443
Alex Klein1699fab2022-09-08 08:46:06 -0600444 self.recov_image_mock.assert_called_with(
445 board="board",
446 image_path=self.build_result.images[constants.IMAGE_TYPE_BASE],
447 )
Joseph Sussman34c01be2022-06-03 14:04:41 +0000448
Alex Klein2557b4f2019-07-11 14:34:00 -0600449
Alex Klein1699fab2022-09-08 08:46:06 -0600450class ImageSignerTestTest(
451 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
452):
453 """Image signer test tests."""
Michael Mortensenc83c9952019-08-05 12:15:12 -0600454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 def setUp(self):
456 self.image_path = os.path.join(self.tempdir, "image.bin")
457 self.result_directory = os.path.join(self.tempdir, "results")
Michael Mortensenc83c9952019-08-05 12:15:12 -0600458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 osutils.SafeMakedirs(self.result_directory)
460 osutils.Touch(self.image_path)
Michael Mortensenc83c9952019-08-05 12:15:12 -0600461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 def testValidateOnly(self):
463 """Sanity check that validate-only calls don't execute any logic."""
464 patch = self.PatchObject(image_lib, "SecurityTest", return_value=True)
465 input_proto = image_pb2.TestImageRequest()
466 input_proto.image.path = self.image_path
467 output_proto = image_pb2.TestImageResult()
Alex Klein231d2da2019-07-22 16:44:45 -0600468
Alex Klein1699fab2022-09-08 08:46:06 -0600469 image_controller.SignerTest(
470 input_proto, output_proto, self.validate_only_config
471 )
Alex Klein231d2da2019-07-22 16:44:45 -0600472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600474
Alex Klein1699fab2022-09-08 08:46:06 -0600475 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700476 """Test mock call does not execute any logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600477 patch = self.PatchObject(image_lib, "SecurityTest", return_value=True)
478 input_proto = image_pb2.TestImageRequest()
479 input_proto.image.path = self.image_path
480 output_proto = image_pb2.TestImageResult()
Michael Mortensen10146cf2019-11-19 19:59:22 -0700481
Alex Klein1699fab2022-09-08 08:46:06 -0600482 image_controller.SignerTest(
483 input_proto, output_proto, self.mock_call_config
484 )
Michael Mortensen10146cf2019-11-19 19:59:22 -0700485
Alex Klein1699fab2022-09-08 08:46:06 -0600486 patch.assert_not_called()
487 self.assertEqual(output_proto.success, True)
Michael Mortensen10146cf2019-11-19 19:59:22 -0700488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 def testMockError(self):
490 """Test that mock call does not execute any logic, returns error."""
491 patch = self.PatchObject(image_lib, "SecurityTest", return_value=True)
492 input_proto = image_pb2.TestImageRequest()
493 input_proto.image.path = self.image_path
494 output_proto = image_pb2.TestImageResult()
Michael Mortensen85d38402019-12-12 09:50:29 -0700495
Alex Klein1699fab2022-09-08 08:46:06 -0600496 rc = image_controller.SignerTest(
497 input_proto, output_proto, self.mock_error_config
498 )
Michael Mortensen85d38402019-12-12 09:50:29 -0700499
Alex Klein1699fab2022-09-08 08:46:06 -0600500 patch.assert_not_called()
501 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Michael Mortensen85d38402019-12-12 09:50:29 -0700502
Alex Klein1699fab2022-09-08 08:46:06 -0600503 def testSignerTestNoImage(self):
504 """Test function argument validation."""
505 input_proto = image_pb2.TestImageRequest()
506 output_proto = image_pb2.TestImageResult()
Michael Mortensenc83c9952019-08-05 12:15:12 -0600507
Alex Klein1699fab2022-09-08 08:46:06 -0600508 # Nothing provided.
509 with self.assertRaises(cros_build_lib.DieSystemExit):
510 image_controller.SignerTest(
511 input_proto, output_proto, self.api_config
512 )
Michael Mortensenc83c9952019-08-05 12:15:12 -0600513
Alex Klein1699fab2022-09-08 08:46:06 -0600514 def testSignerTestSuccess(self):
515 """Test successful call handling."""
516 self.PatchObject(image_lib, "SecurityTest", return_value=True)
517 input_proto = image_pb2.TestImageRequest()
518 input_proto.image.path = self.image_path
519 output_proto = image_pb2.TestImageResult()
Michael Mortensenc83c9952019-08-05 12:15:12 -0600520
Alex Klein1699fab2022-09-08 08:46:06 -0600521 image_controller.SignerTest(input_proto, output_proto, self.api_config)
Alex Klein231d2da2019-07-22 16:44:45 -0600522
Alex Klein1699fab2022-09-08 08:46:06 -0600523 def testSignerTestFailure(self):
524 """Test function output tests."""
525 input_proto = image_pb2.TestImageRequest()
526 input_proto.image.path = self.image_path
527 output_proto = image_pb2.TestImageResult()
Michael Mortensenc83c9952019-08-05 12:15:12 -0600528
Alex Klein1699fab2022-09-08 08:46:06 -0600529 self.PatchObject(image_lib, "SecurityTest", return_value=False)
530 image_controller.SignerTest(input_proto, output_proto, self.api_config)
531 self.assertFalse(output_proto.success)
Michael Mortensenc83c9952019-08-05 12:15:12 -0600532
Michael Mortensenc83c9952019-08-05 12:15:12 -0600533
Alex Klein1699fab2022-09-08 08:46:06 -0600534class ImageTestTest(
535 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
536):
537 """Image test tests."""
Alex Klein2966e302019-01-17 13:29:38 -0700538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 def setUp(self):
540 self.image_path = os.path.join(self.tempdir, "image.bin")
541 self.board = "board"
542 self.result_directory = os.path.join(self.tempdir, "results")
Alex Klein2966e302019-01-17 13:29:38 -0700543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 osutils.SafeMakedirs(self.result_directory)
545 osutils.Touch(self.image_path)
Alex Klein2966e302019-01-17 13:29:38 -0700546
Alex Klein1699fab2022-09-08 08:46:06 -0600547 def testValidateOnly(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700548 """Verify a validate-only call does not execute any logic."""
Alex Klein1699fab2022-09-08 08:46:06 -0600549 patch = self.PatchObject(image_service, "Test")
Alex Klein231d2da2019-07-22 16:44:45 -0600550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 input_proto = image_pb2.TestImageRequest()
552 input_proto.image.path = self.image_path
553 input_proto.build_target.name = self.board
554 input_proto.result.directory = self.result_directory
555 output_proto = image_pb2.TestImageResult()
Alex Klein231d2da2019-07-22 16:44:45 -0600556
Alex Klein1699fab2022-09-08 08:46:06 -0600557 image_controller.Test(
558 input_proto, output_proto, self.validate_only_config
559 )
560 patch.assert_not_called()
Alex Klein231d2da2019-07-22 16:44:45 -0600561
Alex Klein1699fab2022-09-08 08:46:06 -0600562 def testMockCall(self):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700563 """Test mock call does not execute any logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600564 patch = self.PatchObject(image_service, "Test")
Michael Mortensen10146cf2019-11-19 19:59:22 -0700565
Alex Klein1699fab2022-09-08 08:46:06 -0600566 input_proto = image_pb2.TestImageRequest()
567 input_proto.image.path = self.image_path
568 input_proto.build_target.name = self.board
569 input_proto.result.directory = self.result_directory
570 output_proto = image_pb2.TestImageResult()
Michael Mortensen10146cf2019-11-19 19:59:22 -0700571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 image_controller.Test(input_proto, output_proto, self.mock_call_config)
573 patch.assert_not_called()
574 self.assertEqual(output_proto.success, True)
Michael Mortensen10146cf2019-11-19 19:59:22 -0700575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 def testMockError(self):
577 """Test that mock call does not execute any logic, returns error."""
578 patch = self.PatchObject(image_service, "Test")
Michael Mortensen85d38402019-12-12 09:50:29 -0700579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 input_proto = image_pb2.TestImageRequest()
581 input_proto.image.path = self.image_path
582 input_proto.build_target.name = self.board
583 input_proto.result.directory = self.result_directory
584 output_proto = image_pb2.TestImageResult()
Michael Mortensen85d38402019-12-12 09:50:29 -0700585
Alex Klein1699fab2022-09-08 08:46:06 -0600586 rc = image_controller.Test(
587 input_proto, output_proto, self.mock_error_config
588 )
589 patch.assert_not_called()
590 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Michael Mortensen85d38402019-12-12 09:50:29 -0700591
Alex Klein1699fab2022-09-08 08:46:06 -0600592 def testTestArgumentValidation(self):
593 """Test function argument validation tests."""
594 self.PatchObject(image_service, "Test", return_value=True)
595 input_proto = image_pb2.TestImageRequest()
596 output_proto = image_pb2.TestImageResult()
Alex Klein2966e302019-01-17 13:29:38 -0700597
Alex Klein1699fab2022-09-08 08:46:06 -0600598 # Nothing provided.
599 with self.assertRaises(cros_build_lib.DieSystemExit):
600 image_controller.Test(input_proto, output_proto, self.api_config)
Alex Klein2966e302019-01-17 13:29:38 -0700601
Alex Klein1699fab2022-09-08 08:46:06 -0600602 # Just one argument.
603 input_proto.build_target.name = self.board
604 with self.assertRaises(cros_build_lib.DieSystemExit):
605 image_controller.Test(input_proto, output_proto, self.api_config)
Alex Klein2966e302019-01-17 13:29:38 -0700606
Alex Klein1699fab2022-09-08 08:46:06 -0600607 # Two arguments provided.
608 input_proto.result.directory = self.result_directory
609 with self.assertRaises(cros_build_lib.DieSystemExit):
610 image_controller.Test(input_proto, output_proto, self.api_config)
Alex Klein2966e302019-01-17 13:29:38 -0700611
Alex Klein1699fab2022-09-08 08:46:06 -0600612 # Invalid image path.
613 input_proto.image.path = "/invalid/image/path"
614 with self.assertRaises(cros_build_lib.DieSystemExit):
615 image_controller.Test(input_proto, output_proto, self.api_config)
Alex Klein2966e302019-01-17 13:29:38 -0700616
Alex Klein1699fab2022-09-08 08:46:06 -0600617 # All valid arguments.
618 input_proto.image.path = self.image_path
619 image_controller.Test(input_proto, output_proto, self.api_config)
Alex Klein2966e302019-01-17 13:29:38 -0700620
Alex Klein1699fab2022-09-08 08:46:06 -0600621 def testTestOutputHandling(self):
622 """Test function output tests."""
623 input_proto = image_pb2.TestImageRequest()
624 input_proto.image.path = self.image_path
625 input_proto.build_target.name = self.board
626 input_proto.result.directory = self.result_directory
627 output_proto = image_pb2.TestImageResult()
Alex Klein2966e302019-01-17 13:29:38 -0700628
Alex Klein1699fab2022-09-08 08:46:06 -0600629 self.PatchObject(image_service, "Test", return_value=True)
630 image_controller.Test(input_proto, output_proto, self.api_config)
631 self.assertTrue(output_proto.success)
Alex Klein2966e302019-01-17 13:29:38 -0700632
Alex Klein1699fab2022-09-08 08:46:06 -0600633 self.PatchObject(image_service, "Test", return_value=False)
634 image_controller.Test(input_proto, output_proto, self.api_config)
635 self.assertFalse(output_proto.success)
Jack Neus761e1842020-12-01 18:20:11 +0000636
637
Jack Neusaccffec2023-08-31 15:30:49 +0000638class PushImageTest(cros_test_lib.MockTestCase, api_config.ApiConfigMixin):
Alex Klein1699fab2022-09-08 08:46:06 -0600639 """Push image test."""
Jack Neus761e1842020-12-01 18:20:11 +0000640
Alex Klein1699fab2022-09-08 08:46:06 -0600641 def _GetRequest(
642 self,
643 gs_image_dir="gs://chromeos-image-archive/atlas-release/R89-13604.0.0",
644 build_target_name="atlas",
645 profile="foo",
646 sign_types=None,
647 dryrun=True,
648 channels=None,
649 ):
650 return image_pb2.PushImageRequest(
651 gs_image_dir=gs_image_dir,
652 sysroot=sysroot_pb2.Sysroot(
653 build_target=common_pb2.BuildTarget(name=build_target_name)
654 ),
655 profile=common_pb2.Profile(name=profile),
656 sign_types=sign_types,
657 dryrun=dryrun,
658 channels=channels,
659 )
Jack Neus761e1842020-12-01 18:20:11 +0000660
Alex Klein1699fab2022-09-08 08:46:06 -0600661 def _GetResponse(self):
Jack Neusaccffec2023-08-31 15:30:49 +0000662 return image_pb2.PushImageResponse()
Jack Neus761e1842020-12-01 18:20:11 +0000663
Alex Klein1699fab2022-09-08 08:46:06 -0600664 @mock.patch.object(pushimage, "PushImage", return_value={})
665 def testValidateOnly(self, MockPushImage):
666 """Check that a validate only call does not execute any logic."""
667 req = self._GetRequest(
668 sign_types=[
669 common_pb2.IMAGE_TYPE_RECOVERY,
670 common_pb2.IMAGE_TYPE_FACTORY,
671 common_pb2.IMAGE_TYPE_FIRMWARE,
672 common_pb2.IMAGE_TYPE_ACCESSORY_USBPD,
673 common_pb2.IMAGE_TYPE_ACCESSORY_RWSIG,
674 common_pb2.IMAGE_TYPE_BASE,
675 common_pb2.IMAGE_TYPE_GSC_FIRMWARE,
676 common_pb2.IMAGE_TYPE_HPS_FIRMWARE,
677 ]
678 )
679 rc = image_controller.PushImage(
Jack Neusaccffec2023-08-31 15:30:49 +0000680 req, self._GetResponse(), self.validate_only_config
Alex Klein1699fab2022-09-08 08:46:06 -0600681 )
682 MockPushImage.assert_not_called()
683 self.assertEqual(rc, controller.RETURN_CODE_VALID_INPUT)
Jack Neus761e1842020-12-01 18:20:11 +0000684
Alex Klein1699fab2022-09-08 08:46:06 -0600685 @mock.patch.object(pushimage, "PushImage", return_value={})
686 def testValidateOnlyInvalid(self, MockPushImage):
687 """Check that validate call rejects invalid sign types."""
688 # Pass unsupported image type.
689 req = self._GetRequest(sign_types=[common_pb2.IMAGE_TYPE_DLC])
690 rc = image_controller.PushImage(
691 req, self._GetResponse(), self.validate_only_config
692 )
693 MockPushImage.assert_not_called()
694 self.assertEqual(rc, controller.RETURN_CODE_INVALID_INPUT)
Jack Neus761e1842020-12-01 18:20:11 +0000695
Alex Klein1699fab2022-09-08 08:46:06 -0600696 @mock.patch.object(pushimage, "PushImage", return_value={})
697 def testMockCall(self, MockPushImage):
Alex Kleinab87ceb2023-01-24 12:00:51 -0700698 """Test mock call does not execute any logic, returns mocked value."""
Alex Klein1699fab2022-09-08 08:46:06 -0600699 rc = image_controller.PushImage(
700 self._GetRequest(), self._GetResponse(), self.mock_call_config
701 )
702 MockPushImage.assert_not_called()
703 self.assertEqual(controller.RETURN_CODE_SUCCESS, rc)
Jack Neus761e1842020-12-01 18:20:11 +0000704
Alex Klein1699fab2022-09-08 08:46:06 -0600705 @mock.patch.object(pushimage, "PushImage", return_value={})
706 def testMockError(self, MockPushImage):
707 """Test that mock call does not execute any logic, returns error."""
708 rc = image_controller.PushImage(
709 self._GetRequest(), self._GetResponse(), self.mock_error_config
710 )
711 MockPushImage.assert_not_called()
712 self.assertEqual(controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY, rc)
Jack Neus761e1842020-12-01 18:20:11 +0000713
Alex Klein1699fab2022-09-08 08:46:06 -0600714 @mock.patch.object(pushimage, "PushImage", return_value={})
715 def testNoBuildTarget(self, _):
716 """Test no build target given fails."""
717 request = self._GetRequest(build_target_name="")
718 with self.assertRaises(cros_build_lib.DieSystemExit):
719 image_controller.PushImage(
720 request, self._GetResponse(), self.api_config
721 )
Jack Neus761e1842020-12-01 18:20:11 +0000722
Alex Klein1699fab2022-09-08 08:46:06 -0600723 @mock.patch.object(pushimage, "PushImage", return_value={})
724 def testNoGsImageDir(self, _):
725 """Test no image dir given fails."""
726 request = self._GetRequest(gs_image_dir="")
727 with self.assertRaises(cros_build_lib.DieSystemExit):
728 image_controller.PushImage(
729 request, self._GetResponse(), self.api_config
730 )
Jack Neus761e1842020-12-01 18:20:11 +0000731
Alex Klein1699fab2022-09-08 08:46:06 -0600732 @mock.patch.object(pushimage, "PushImage", return_value={})
733 def testCallCorrect(self, MockPushImage):
734 """Check that a call is called with the correct parameters."""
735 request = self._GetRequest(
736 dryrun=False,
737 profile="",
738 sign_types=[common_pb2.IMAGE_TYPE_RECOVERY],
739 channels=[common_pb2.CHANNEL_DEV, common_pb2.CHANNEL_CANARY],
740 )
741 request.dest_bucket = "gs://foo"
742 image_controller.PushImage(
743 request, self._GetResponse(), self.api_config
744 )
745 MockPushImage.assert_called_with(
746 request.gs_image_dir,
747 request.sysroot.build_target.name,
Mike Frysinger9d1253c2023-02-02 09:00:07 -0500748 dryrun=request.dryrun,
Alex Klein1699fab2022-09-08 08:46:06 -0600749 sign_types=["recovery"],
750 dest_bucket=request.dest_bucket,
751 force_channels=["dev", "canary"],
752 )
Jack Neus761e1842020-12-01 18:20:11 +0000753
Alex Klein1699fab2022-09-08 08:46:06 -0600754 @mock.patch.object(
755 pushimage,
756 "PushImage",
757 return_value={
758 "dev": ["gs://dev/instr1", "gs://dev/instr2"],
759 "canary": ["gs://canary/instr1"],
760 },
761 )
762 def testOutput(self, _):
763 """Check that a call populates the response object."""
764 request = self._GetRequest(
765 profile="",
766 sign_types=[common_pb2.IMAGE_TYPE_RECOVERY],
767 channels=[common_pb2.CHANNEL_DEV, common_pb2.CHANNEL_CANARY],
768 )
769 request.dest_bucket = "gs://foo"
770 response = self._GetResponse()
771 image_controller.PushImage(request, response, self.api_config)
772 self.assertEqual(
773 sorted([i.instructions_file_path for i in response.instructions]),
774 sorted(
775 ["gs://dev/instr1", "gs://dev/instr2", "gs://canary/instr1"]
776 ),
777 )
Greg Edelston6902e3d2022-01-27 12:19:38 -0700778
Jack Neusaccffec2023-08-31 15:30:49 +0000779 def testCallSucceeds(self):
Alex Klein1699fab2022-09-08 08:46:06 -0600780 """Check that a (dry run) call is made successfully."""
781 request = self._GetRequest(sign_types=[common_pb2.IMAGE_TYPE_RECOVERY])
782 rc = image_controller.PushImage(
783 request, self._GetResponse(), self.api_config
784 )
785 self.assertEqual(rc, controller.RETURN_CODE_SUCCESS)
Jack Neus761e1842020-12-01 18:20:11 +0000786
Alex Klein1699fab2022-09-08 08:46:06 -0600787 def testCallFailsWithBadImageDir(self):
788 """Check that a (dry run) call fails when given a bad gs_image_dir."""
789 request = self._GetRequest(gs_image_dir="foo")
790 rc = image_controller.PushImage(
791 request, self._GetResponse, self.api_config
792 )
793 self.assertEqual(rc, controller.RETURN_CODE_COMPLETED_UNSUCCESSFULLY)
Jack Neusfa4eca12023-08-30 20:10:08 +0000794
795
Jack Neus8b28d462023-09-27 18:02:25 +0000796class SignImageTest(
797 cros_test_lib.MockTempDirTestCase, api_config.ApiConfigMixin
798):
Jack Neusfa4eca12023-08-30 20:10:08 +0000799 """Sign image test."""
800
801 def testValidateOnly(self):
802 """Check that a validate only call does not execute any logic."""
Benjamin Shai30b5e952023-09-19 22:28:19 +0000803 req = image_pb2.SignImageRequest(
Jack Neus8b28d462023-09-27 18:02:25 +0000804 archive_dir=str(self.tempdir),
Benjamin Shai30b5e952023-09-19 22:28:19 +0000805 result_path=common_pb2.ResultPath(
806 path=common_pb2.Path(
807 path="/path/to/outside",
808 location=common_pb2.Path.OUTSIDE,
809 )
Jack Neus8b28d462023-09-27 18:02:25 +0000810 ),
Benjamin Shai30b5e952023-09-19 22:28:19 +0000811 )
Jack Neusfa4eca12023-08-30 20:10:08 +0000812 resp = image_pb2.SignImageResponse()
813 rc = image_controller.SignImage(req, resp, self.validate_only_config)
814 self.assertEqual(rc, controller.RETURN_CODE_VALID_INPUT)
815
Jack Neusbbf75e02023-10-05 16:01:38 +0000816 @mock.patch.object(image_controller.image, "SignImage")
817 def testSuccess(self, mock_sign_image: mock.MagicMock):
Jack Neusfa4eca12023-08-30 20:10:08 +0000818 """Check that the endpoint finishes successfully."""
Jack Neusbbf75e02023-10-05 16:01:38 +0000819 docker_image = "us-docker.pkg.dev/chromeos-bot/signing/signing:16963491"
820 req = image_pb2.SignImageRequest(
821 archive_dir=str(self.tempdir),
822 result_path=common_pb2.ResultPath(
823 path=common_pb2.Path(
824 path="/tmp/result_path",
825 location=common_pb2.Path.Location.OUTSIDE,
826 )
827 ),
828 docker_image=docker_image,
829 )
Jack Neusfa4eca12023-08-30 20:10:08 +0000830 resp = image_pb2.SignImageResponse()
Jack Neusbbf75e02023-10-05 16:01:38 +0000831
832 build_target_signed_artifacts = signing_pb2.BuildTargetSignedArtifacts(
833 archive_artifacts=[
834 signing_pb2.ArchiveArtifacts(
835 input_archive_name="foo",
836 )
837 ]
838 )
839 mock_sign_image.return_value = build_target_signed_artifacts
840
841 rc = image_controller.SignImage(req, resp, self.api_config)
Jack Neusfa4eca12023-08-30 20:10:08 +0000842 self.assertEqual(rc, controller.RETURN_CODE_SUCCESS)
Jack Neusbbf75e02023-10-05 16:01:38 +0000843 self.assertEqual(
844 resp,
845 image_pb2.SignImageResponse(
846 output_archive_dir=str(self.tempdir),
847 signed_artifacts=build_target_signed_artifacts,
848 ),
849 )
850
851 mock_sign_image.assert_called_with(
852 mock.ANY, str(self.tempdir), Path("/tmp/result_path"), docker_image
853 )