blob: 7d3a75564b76a2b1df5da6d51ff2c516b69138be [file] [log] [blame]
Alex Kleinf4dc4f52018-12-05 13:55:12 -07001# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Alex Klein2bfacb22019-02-04 11:42:17 -07005"""Tests for the build_api script covering the base Build API functionality."""
6
Alex Klein5bcb4d22019-03-21 13:51:54 -06007import os
Tomasz Tylendab4292302021-08-08 18:59:36 +09008from typing import Callable
Alex Klein5bcb4d22019-03-21 13:51:54 -06009
Mike Frysinger2c024062021-05-22 15:43:22 -040010from chromite.third_party.google.protobuf import json_format
Alex Kleinbd6edf82019-07-18 10:30:49 -060011
Alex Klein69339cc2019-07-22 14:08:35 -060012from chromite.api import api_config
Alex Kleine191ed62020-02-27 15:59:55 -070013from chromite.api import message_util
Alex Klein146d4772019-06-20 13:48:25 -060014from chromite.api import router
Alex Klein7107bdd2019-03-14 17:14:31 -060015from chromite.api.gen.chromite.api import build_api_test_pb2
Alex Kleinc7d647f2020-01-06 12:00:48 -070016from chromite.lib import chroot_lib
Alex Klein2bfacb22019-02-04 11:42:17 -070017from chromite.lib import cros_build_lib
Alex Kleinf4dc4f52018-12-05 13:55:12 -070018from chromite.lib import cros_test_lib
Alex Klein5bcb4d22019-03-21 13:51:54 -060019from chromite.lib import osutils
Alex Kleinf4dc4f52018-12-05 13:55:12 -070020
21
Alex Klein69339cc2019-07-22 14:08:35 -060022class RouterTest(cros_test_lib.RunCommandTempDirTestCase,
23 api_config.ApiConfigMixin):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070024 """Test Router functionality."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070025
26 def setUp(self):
Alex Klein146d4772019-06-20 13:48:25 -060027 self.router = router.Router()
Alex Kleinf4dc4f52018-12-05 13:55:12 -070028 self.router.Register(build_api_test_pb2)
29
Alex Klein00aa8072019-04-15 16:36:00 -060030 self.chroot_dir = os.path.join(self.tempdir, 'chroot')
31 chroot_tmp = os.path.join(self.chroot_dir, 'tmp')
32 # Make the tmp dir for the re-exec inside chroot input/output files.
33 osutils.SafeMakedirs(chroot_tmp)
34
Alex Kleine191ed62020-02-27 15:59:55 -070035 # Build the input/output/config paths we'll be using in the tests.
36 self.json_input_file = os.path.join(self.tempdir, 'input.json')
37 self.json_output_file = os.path.join(self.tempdir, 'output.json')
38 self.json_config_file = os.path.join(self.tempdir, 'config.json')
39 self.binary_input_file = os.path.join(self.tempdir, 'input.bin')
40 self.binary_output_file = os.path.join(self.tempdir, 'output.bin')
41 self.binary_config_file = os.path.join(self.tempdir, 'config.bin')
42
43 # The message handlers for the respective files.
44 self.json_input_handler = message_util.get_message_handler(
45 self.json_input_file, message_util.FORMAT_JSON)
46 self.json_output_handler = message_util.get_message_handler(
47 self.json_output_file, message_util.FORMAT_JSON)
48 self.json_config_handler = message_util.get_message_handler(
49 self.json_config_file, message_util.FORMAT_JSON)
50 self.binary_input_handler = message_util.get_message_handler(
51 self.binary_input_file, message_util.FORMAT_BINARY)
52 self.binary_output_handler = message_util.get_message_handler(
53 self.binary_output_file, message_util.FORMAT_BINARY)
54 self.binary_config_handler = message_util.get_message_handler(
55 self.binary_config_file, message_util.FORMAT_BINARY)
56
57 # Build an input message to use.
58 self.expected_id = 'input id'
59 input_msg = build_api_test_pb2.TestRequestMessage()
60 input_msg.id = self.expected_id
61 input_msg.chroot.path = self.chroot_dir
62
63 # Write out base input and config messages.
64 osutils.WriteFile(self.json_input_file,
65 json_format.MessageToJson(input_msg))
66 osutils.WriteFile(
67 self.binary_input_file, input_msg.SerializeToString(), mode='wb')
68
69 config_msg = self.api_config.get_proto()
70 osutils.WriteFile(self.json_config_file,
71 json_format.MessageToJson(config_msg))
72 osutils.WriteFile(
73 self.binary_config_file, config_msg.SerializeToString(), mode='wb')
Alex Kleinbd6edf82019-07-18 10:30:49 -060074
75 self.subprocess_tempdir = os.path.join(self.chroot_dir, 'tempdir')
76 osutils.SafeMakedirs(self.subprocess_tempdir)
Alex Klein5bcb4d22019-03-21 13:51:54 -060077
Alex Kleine191ed62020-02-27 15:59:55 -070078 def testJsonInputOutputMethod(self):
79 """Test json input/output handling."""
Alex Klein69339cc2019-07-22 14:08:35 -060080 def impl(input_msg, output_msg, config):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070081 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
82 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
Alex Klein69339cc2019-07-22 14:08:35 -060083 self.assertIsInstance(config, api_config.ApiConfig)
Alex Kleine191ed62020-02-27 15:59:55 -070084 self.assertEqual(config, self.api_config)
Alex Kleinf4dc4f52018-12-05 13:55:12 -070085
86 self.PatchObject(self.router, '_GetMethod', return_value=impl)
87
Alex Kleine191ed62020-02-27 15:59:55 -070088 self.router.Route(
89 'chromite.api.TestApiService',
90 'InputOutputMethod',
91 self.api_config,
92 self.json_input_handler,
93 [self.json_output_handler],
94 self.json_config_handler)
95
96 def testBinaryInputOutputMethod(self):
97 """Test binary input/output handling."""
98 def impl(input_msg, output_msg, config):
99 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
100 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
101 self.assertIsInstance(config, api_config.ApiConfig)
102 self.assertEqual(config, self.api_config)
103
104 self.PatchObject(self.router, '_GetMethod', return_value=impl)
105
106 self.router.Route(
107 'chromite.api.TestApiService',
108 'InputOutputMethod',
109 self.api_config,
110 self.binary_input_handler,
111 [self.binary_output_handler],
112 self.binary_config_handler)
113
114 def testMultipleOutputHandling(self):
115 """Test multiple output handling."""
116 expected_result = 'Success!'
117
118 def impl(input_msg, output_msg, config):
119 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
120 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
121 self.assertIsInstance(config, api_config.ApiConfig)
122 self.assertEqual(config, self.api_config)
123 # Set the property on the output to test against.
124 output_msg.result = expected_result
125
126 self.PatchObject(self.router, '_GetMethod', return_value=impl)
127
128 self.router.Route(
129 'chromite.api.TestApiService',
130 'InputOutputMethod',
131 self.api_config,
132 self.binary_input_handler,
133 [self.binary_output_handler, self.json_output_handler],
134 self.binary_config_handler)
135
136 # Make sure it did write out all the expected files.
137 self.assertExists(self.binary_output_file)
138 self.assertExists(self.json_output_file)
139
140 # Parse the output files back into a message.
141 binary_msg = build_api_test_pb2.TestResultMessage()
142 json_msg = build_api_test_pb2.TestResultMessage()
143 self.binary_output_handler.read_into(binary_msg)
144 self.json_output_handler.read_into(json_msg)
145
146 # Make sure the parsed messages have the expected content.
147 self.assertEqual(binary_msg.result, expected_result)
148 self.assertEqual(json_msg.result, expected_result)
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700149
Alex Klein2bfacb22019-02-04 11:42:17 -0700150 def testRenameMethod(self):
151 """Test implementation name config."""
152 def _GetMethod(_, method_name):
153 self.assertEqual('CorrectName', method_name)
Alex Klein69339cc2019-07-22 14:08:35 -0600154 return lambda x, y, z: None
Alex Klein2bfacb22019-02-04 11:42:17 -0700155
156 self.PatchObject(self.router, '_GetMethod', side_effect=_GetMethod)
157
158 self.router.Route('chromite.api.TestApiService', 'RenamedMethod',
Alex Kleine191ed62020-02-27 15:59:55 -0700159 self.api_config, self.binary_input_handler,
160 [self.binary_output_handler], self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700161
Tomasz Tylendab4292302021-08-08 18:59:36 +0900162 def _mock_callable(self, expect_called: bool) -> Callable:
Alex Klein00aa8072019-04-15 16:36:00 -0600163 """Helper to create the implementation mock to test chroot assertions.
164
165 Args:
Tomasz Tylendab4292302021-08-08 18:59:36 +0900166 expect_called: Whether the implementation should be called.
Alex Klein00aa8072019-04-15 16:36:00 -0600167 When False, an assertion will fail if it is called.
168
169 Returns:
Tomasz Tylendab4292302021-08-08 18:59:36 +0900170 The implementation.
Alex Klein00aa8072019-04-15 16:36:00 -0600171 """
Alex Klein69339cc2019-07-22 14:08:35 -0600172 def impl(_input_msg, _output_msg, _config):
Alex Klein00aa8072019-04-15 16:36:00 -0600173 self.assertTrue(expect_called,
Alex Klein2bfacb22019-02-04 11:42:17 -0700174 'The implementation should not have been called.')
Alex Klein2bfacb22019-02-04 11:42:17 -0700175
Alex Klein00aa8072019-04-15 16:36:00 -0600176 return impl
Alex Klein2bfacb22019-02-04 11:42:17 -0700177
Alex Kleine191ed62020-02-27 15:59:55 -0700178 def _writeChrootCallOutput(self, content='{}', mode='w'):
Alex Kleinbd6edf82019-07-18 10:30:49 -0600179 def impl(*_args, **_kwargs):
180 """Side effect for inside-chroot calls to the API."""
181 osutils.WriteFile(
182 os.path.join(self.subprocess_tempdir,
183 router.Router.REEXEC_OUTPUT_FILE),
Alex Kleine191ed62020-02-27 15:59:55 -0700184 content,
185 mode=mode)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600186
187 return impl
188
Alex Klein00aa8072019-04-15 16:36:00 -0600189 def testInsideServiceInsideMethodInsideChroot(self):
190 """Test inside/inside/inside works correctly."""
191 self.PatchObject(self.router, '_GetMethod',
192 return_value=self._mock_callable(expect_called=True))
193 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700194 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700195 'InsideServiceInsideMethod', self.api_config,
196 self.binary_input_handler, [self.binary_output_handler],
197 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700198
Alex Klein00aa8072019-04-15 16:36:00 -0600199 def testInsideServiceOutsideMethodOutsideChroot(self):
200 """Test the outside method override works as expected."""
201 self.PatchObject(self.router, '_GetMethod',
202 return_value=self._mock_callable(expect_called=True))
203 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
204 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700205 'InsideServiceOutsideMethod', self.api_config,
206 self.binary_input_handler, [self.binary_output_handler],
207 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600208
209 def testInsideServiceInsideMethodOutsideChroot(self):
210 """Test calling an inside method from outside the chroot."""
211 self.PatchObject(self.router, '_GetMethod',
212 return_value=self._mock_callable(expect_called=False))
213 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600214
215 service = 'chromite.api.InsideChrootApiService'
216 method = 'InsideServiceInsideMethod'
217 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700218 self.router.Route(service, method, self.api_config,
219 self.binary_input_handler, [self.binary_output_handler],
220 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600221
Alex Kleinc05f3d12019-05-29 14:16:21 -0600222 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Klein00aa8072019-04-15 16:36:00 -0600223
224 def testInsideServiceOutsideMethodInsideChroot(self):
225 """Test inside chroot for outside method raises an error."""
226 self.PatchObject(self.router, '_GetMethod',
227 return_value=self._mock_callable(expect_called=False))
228 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700229 with self.assertRaises(cros_build_lib.DieSystemExit):
230 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700231 'InsideServiceOutsideMethod', self.api_config,
232 self.binary_input_handler, [self.binary_output_handler],
233 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700234
Alex Klein00aa8072019-04-15 16:36:00 -0600235 def testOutsideServiceOutsideMethodOutsideChroot(self):
236 """Test outside/outside/outside works correctly."""
237 self.PatchObject(self.router, '_GetMethod',
238 return_value=self._mock_callable(expect_called=True))
239 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein2bfacb22019-02-04 11:42:17 -0700240 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700241 'OutsideServiceOutsideMethod', self.api_config,
242 self.binary_input_handler, [self.binary_output_handler],
243 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700244
Alex Klein00aa8072019-04-15 16:36:00 -0600245 def testOutsideServiceInsideMethodInsideChroot(self):
246 """Test the inside method assertion override works properly."""
247 self.PatchObject(self.router, '_GetMethod',
248 return_value=self._mock_callable(expect_called=True))
249 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700250 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700251 'OutsideServiceInsideMethod', self.api_config,
252 self.binary_input_handler, [self.binary_output_handler],
253 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600254
255 def testOutsideServiceInsideMethodOutsideChroot(self):
256 """Test calling an inside override method from outside the chroot."""
257 self.PatchObject(self.router, '_GetMethod',
258 return_value=self._mock_callable(expect_called=False))
259 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600260
261 service = 'chromite.api.OutsideChrootApiService'
262 method = 'OutsideServiceInsideMethod'
263 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700264 self.router.Route(service, method, self.api_config,
265 self.binary_input_handler, [self.binary_output_handler],
266 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600267
Alex Kleinc05f3d12019-05-29 14:16:21 -0600268 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600269
270 def testReexecNonemptyOutput(self):
271 """Test calling an inside chroot method that produced output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600272 self.PatchObject(self.router, '_GetMethod',
273 return_value=self._mock_callable(expect_called=False))
274 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Kleinc7d647f2020-01-06 12:00:48 -0700275
276 # Patch the chroot tempdir method to return a tempdir with our subprocess
277 # tempdir so the output file will be in the expected location.
278 tempdir = osutils.TempDir()
279 original = tempdir.tempdir
280 tempdir.tempdir = self.subprocess_tempdir
281 self.PatchObject(chroot_lib.Chroot, 'tempdir', return_value=tempdir)
282
Alex Kleine191ed62020-02-27 15:59:55 -0700283 expected_output_msg = build_api_test_pb2.TestResultMessage()
284 expected_output_msg.result = 'foo'
285
286 # Set the command side effect to write out our expected output to the
287 # output file for the inside the chroot reexecution of the endpoint.
288 # This lets us make sure the logic moving everything out works as intended.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600289 self.rc.SetDefaultCmdResult(
Alex Kleine191ed62020-02-27 15:59:55 -0700290 side_effect=self._writeChrootCallOutput(
291 content=expected_output_msg.SerializeToString(), mode='wb'))
Alex Kleinbd6edf82019-07-18 10:30:49 -0600292
293 service = 'chromite.api.OutsideChrootApiService'
294 method = 'OutsideServiceInsideMethod'
295 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700296 self.router.Route(service, method, self.api_config,
297 self.binary_input_handler, [self.binary_output_handler],
298 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600299
300 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
301
302 # It should be writing the result out to our output file.
Alex Kleine191ed62020-02-27 15:59:55 -0700303 output_msg = build_api_test_pb2.TestResultMessage()
304 self.binary_output_handler.read_into(output_msg)
305 self.assertEqual(expected_output_msg, output_msg)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600306
Alex Kleinc7d647f2020-01-06 12:00:48 -0700307 tempdir.tempdir = original
308 del tempdir
309
Alex Kleinbd6edf82019-07-18 10:30:49 -0600310 def testReexecEmptyOutput(self):
311 """Test calling an inside chroot method that produced no output."""
Alex Kleine191ed62020-02-27 15:59:55 -0700312 self.PatchObject(self.router, '_GetMethod',
313 return_value=self._mock_callable(expect_called=False))
314 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
315 expected_output_msg = build_api_test_pb2.TestResultMessage()
316
317 # Set the command side effect to write out our expected output to the
318 # output file for the inside the chroot reexecution of the endpoint.
319 # This lets us make sure the logic moving everything out works as intended.
320 self.rc.SetDefaultCmdResult(
321 side_effect=self._writeChrootCallOutput(
322 content=expected_output_msg.SerializeToString(), mode='wb'))
323
324 service = 'chromite.api.OutsideChrootApiService'
325 method = 'OutsideServiceInsideMethod'
326 service_method = '%s/%s' % (service, method)
327 self.router.Route(service, method, self.api_config,
328 self.binary_input_handler, [self.binary_output_handler],
329 self.binary_config_handler)
330
331 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
332
333 output_msg = build_api_test_pb2.TestResultMessage()
334 self.binary_output_handler.read_into(output_msg)
335 self.assertEqual(expected_output_msg, output_msg)
336
337 def testReexecNoOutput(self):
338 """Test calling an inside chroot method that produced no output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600339 self.PatchObject(self.router, '_GetMethod',
340 return_value=self._mock_callable(expect_called=False))
341 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
342 self.rc.SetDefaultCmdResult(returncode=1)
343
344 service = 'chromite.api.OutsideChrootApiService'
345 method = 'OutsideServiceInsideMethod'
346 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700347 self.router.Route(service, method, self.api_config,
348 self.binary_input_handler, [self.binary_output_handler],
349 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600350
351 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleine191ed62020-02-27 15:59:55 -0700352
353 output_msg = build_api_test_pb2.TestResultMessage()
354 empty_msg = build_api_test_pb2.TestResultMessage()
355 self.binary_output_handler.read_into(output_msg)
356 self.assertEqual(empty_msg, output_msg)
Alex Klein6431d3a2019-08-29 10:16:08 -0600357
358 def testInvalidService(self):
359 """Test invalid service call."""
360 service = 'chromite.api.DoesNotExist'
361 method = 'OutsideServiceInsideMethod'
362
363 with self.assertRaises(router.UnknownServiceError):
Alex Kleine191ed62020-02-27 15:59:55 -0700364 self.router.Route(service, method, self.api_config,
365 self.binary_input_handler, [self.binary_output_handler],
366 self.binary_config_handler)
Alex Klein6431d3a2019-08-29 10:16:08 -0600367
368 def testInvalidMethod(self):
369 """Test invalid method call."""
370 service = 'chromite.api.OutsideChrootApiService'
371 method = 'DoesNotExist'
372
373 with self.assertRaises(router.UnknownMethodError):
Alex Kleine191ed62020-02-27 15:59:55 -0700374 self.router.Route(service, method, self.api_config,
375 self.binary_input_handler, [self.binary_output_handler],
376 self.binary_config_handler)
Alex Klein6cce6f62021-03-02 14:24:05 -0700377
378 def testListVisibility(self):
379 """Test visibility options."""
380 service = 'HiddenService'
381 method = 'HiddenMethod'
382
383 for current in self.router.ListMethods():
384 self.assertNotIn(service, current)
385 self.assertNotIn(method, current)