blob: bc4f08d27143b7ba6fdb93bb88a739cd55650a98 [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
8
Alex Kleinbd6edf82019-07-18 10:30:49 -06009from google.protobuf import json_format
10
Alex Klein69339cc2019-07-22 14:08:35 -060011from chromite.api import api_config
Alex Kleine191ed62020-02-27 15:59:55 -070012from chromite.api import message_util
Alex Klein146d4772019-06-20 13:48:25 -060013from chromite.api import router
Alex Klein7107bdd2019-03-14 17:14:31 -060014from chromite.api.gen.chromite.api import build_api_test_pb2
Alex Kleinc7d647f2020-01-06 12:00:48 -070015from chromite.lib import chroot_lib
Alex Klein2bfacb22019-02-04 11:42:17 -070016from chromite.lib import cros_build_lib
Alex Kleinf4dc4f52018-12-05 13:55:12 -070017from chromite.lib import cros_test_lib
Alex Klein5bcb4d22019-03-21 13:51:54 -060018from chromite.lib import osutils
Alex Kleinf4dc4f52018-12-05 13:55:12 -070019
20
Alex Klein69339cc2019-07-22 14:08:35 -060021class RouterTest(cros_test_lib.RunCommandTempDirTestCase,
22 api_config.ApiConfigMixin):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070023 """Test Router functionality."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070024
25 def setUp(self):
Alex Klein146d4772019-06-20 13:48:25 -060026 self.router = router.Router()
Alex Kleinf4dc4f52018-12-05 13:55:12 -070027 self.router.Register(build_api_test_pb2)
28
Alex Klein00aa8072019-04-15 16:36:00 -060029 self.chroot_dir = os.path.join(self.tempdir, 'chroot')
30 chroot_tmp = os.path.join(self.chroot_dir, 'tmp')
31 # Make the tmp dir for the re-exec inside chroot input/output files.
32 osutils.SafeMakedirs(chroot_tmp)
33
Alex Kleine191ed62020-02-27 15:59:55 -070034 # Build the input/output/config paths we'll be using in the tests.
35 self.json_input_file = os.path.join(self.tempdir, 'input.json')
36 self.json_output_file = os.path.join(self.tempdir, 'output.json')
37 self.json_config_file = os.path.join(self.tempdir, 'config.json')
38 self.binary_input_file = os.path.join(self.tempdir, 'input.bin')
39 self.binary_output_file = os.path.join(self.tempdir, 'output.bin')
40 self.binary_config_file = os.path.join(self.tempdir, 'config.bin')
41
42 # The message handlers for the respective files.
43 self.json_input_handler = message_util.get_message_handler(
44 self.json_input_file, message_util.FORMAT_JSON)
45 self.json_output_handler = message_util.get_message_handler(
46 self.json_output_file, message_util.FORMAT_JSON)
47 self.json_config_handler = message_util.get_message_handler(
48 self.json_config_file, message_util.FORMAT_JSON)
49 self.binary_input_handler = message_util.get_message_handler(
50 self.binary_input_file, message_util.FORMAT_BINARY)
51 self.binary_output_handler = message_util.get_message_handler(
52 self.binary_output_file, message_util.FORMAT_BINARY)
53 self.binary_config_handler = message_util.get_message_handler(
54 self.binary_config_file, message_util.FORMAT_BINARY)
55
56 # Build an input message to use.
57 self.expected_id = 'input id'
58 input_msg = build_api_test_pb2.TestRequestMessage()
59 input_msg.id = self.expected_id
60 input_msg.chroot.path = self.chroot_dir
61
62 # Write out base input and config messages.
63 osutils.WriteFile(self.json_input_file,
64 json_format.MessageToJson(input_msg))
65 osutils.WriteFile(
66 self.binary_input_file, input_msg.SerializeToString(), mode='wb')
67
68 config_msg = self.api_config.get_proto()
69 osutils.WriteFile(self.json_config_file,
70 json_format.MessageToJson(config_msg))
71 osutils.WriteFile(
72 self.binary_config_file, config_msg.SerializeToString(), mode='wb')
Alex Kleinbd6edf82019-07-18 10:30:49 -060073
74 self.subprocess_tempdir = os.path.join(self.chroot_dir, 'tempdir')
75 osutils.SafeMakedirs(self.subprocess_tempdir)
Alex Klein5bcb4d22019-03-21 13:51:54 -060076
Alex Kleine191ed62020-02-27 15:59:55 -070077 def testJsonInputOutputMethod(self):
78 """Test json input/output handling."""
Alex Klein69339cc2019-07-22 14:08:35 -060079 def impl(input_msg, output_msg, config):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070080 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
81 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
Alex Klein69339cc2019-07-22 14:08:35 -060082 self.assertIsInstance(config, api_config.ApiConfig)
Alex Kleine191ed62020-02-27 15:59:55 -070083 self.assertEqual(config, self.api_config)
Alex Kleinf4dc4f52018-12-05 13:55:12 -070084
85 self.PatchObject(self.router, '_GetMethod', return_value=impl)
86
Alex Kleine191ed62020-02-27 15:59:55 -070087 self.router.Route(
88 'chromite.api.TestApiService',
89 'InputOutputMethod',
90 self.api_config,
91 self.json_input_handler,
92 [self.json_output_handler],
93 self.json_config_handler)
94
95 def testBinaryInputOutputMethod(self):
96 """Test binary input/output handling."""
97 def impl(input_msg, output_msg, config):
98 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
99 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
100 self.assertIsInstance(config, api_config.ApiConfig)
101 self.assertEqual(config, self.api_config)
102
103 self.PatchObject(self.router, '_GetMethod', return_value=impl)
104
105 self.router.Route(
106 'chromite.api.TestApiService',
107 'InputOutputMethod',
108 self.api_config,
109 self.binary_input_handler,
110 [self.binary_output_handler],
111 self.binary_config_handler)
112
113 def testMultipleOutputHandling(self):
114 """Test multiple output handling."""
115 expected_result = 'Success!'
116
117 def impl(input_msg, output_msg, config):
118 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
119 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
120 self.assertIsInstance(config, api_config.ApiConfig)
121 self.assertEqual(config, self.api_config)
122 # Set the property on the output to test against.
123 output_msg.result = expected_result
124
125 self.PatchObject(self.router, '_GetMethod', return_value=impl)
126
127 self.router.Route(
128 'chromite.api.TestApiService',
129 'InputOutputMethod',
130 self.api_config,
131 self.binary_input_handler,
132 [self.binary_output_handler, self.json_output_handler],
133 self.binary_config_handler)
134
135 # Make sure it did write out all the expected files.
136 self.assertExists(self.binary_output_file)
137 self.assertExists(self.json_output_file)
138
139 # Parse the output files back into a message.
140 binary_msg = build_api_test_pb2.TestResultMessage()
141 json_msg = build_api_test_pb2.TestResultMessage()
142 self.binary_output_handler.read_into(binary_msg)
143 self.json_output_handler.read_into(json_msg)
144
145 # Make sure the parsed messages have the expected content.
146 self.assertEqual(binary_msg.result, expected_result)
147 self.assertEqual(json_msg.result, expected_result)
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700148
Alex Klein2bfacb22019-02-04 11:42:17 -0700149 def testRenameMethod(self):
150 """Test implementation name config."""
151 def _GetMethod(_, method_name):
152 self.assertEqual('CorrectName', method_name)
Alex Klein69339cc2019-07-22 14:08:35 -0600153 return lambda x, y, z: None
Alex Klein2bfacb22019-02-04 11:42:17 -0700154
155 self.PatchObject(self.router, '_GetMethod', side_effect=_GetMethod)
156
157 self.router.Route('chromite.api.TestApiService', 'RenamedMethod',
Alex Kleine191ed62020-02-27 15:59:55 -0700158 self.api_config, self.binary_input_handler,
159 [self.binary_output_handler], self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700160
Alex Klein00aa8072019-04-15 16:36:00 -0600161 def _mock_callable(self, expect_called):
162 """Helper to create the implementation mock to test chroot assertions.
163
164 Args:
165 expect_called (bool): Whether the implementation should be called.
166 When False, an assertion will fail if it is called.
167
168 Returns:
169 callable - The implementation.
170 """
Alex Klein69339cc2019-07-22 14:08:35 -0600171 def impl(_input_msg, _output_msg, _config):
Alex Klein00aa8072019-04-15 16:36:00 -0600172 self.assertTrue(expect_called,
Alex Klein2bfacb22019-02-04 11:42:17 -0700173 'The implementation should not have been called.')
Alex Klein2bfacb22019-02-04 11:42:17 -0700174
Alex Klein00aa8072019-04-15 16:36:00 -0600175 return impl
Alex Klein2bfacb22019-02-04 11:42:17 -0700176
Alex Kleine191ed62020-02-27 15:59:55 -0700177 def _writeChrootCallOutput(self, content='{}', mode='w'):
Alex Kleinbd6edf82019-07-18 10:30:49 -0600178 def impl(*_args, **_kwargs):
179 """Side effect for inside-chroot calls to the API."""
180 osutils.WriteFile(
181 os.path.join(self.subprocess_tempdir,
182 router.Router.REEXEC_OUTPUT_FILE),
Alex Kleine191ed62020-02-27 15:59:55 -0700183 content,
184 mode=mode)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600185
186 return impl
187
Alex Klein00aa8072019-04-15 16:36:00 -0600188 def testInsideServiceInsideMethodInsideChroot(self):
189 """Test inside/inside/inside works correctly."""
190 self.PatchObject(self.router, '_GetMethod',
191 return_value=self._mock_callable(expect_called=True))
192 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700193 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700194 'InsideServiceInsideMethod', self.api_config,
195 self.binary_input_handler, [self.binary_output_handler],
196 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700197
Alex Klein00aa8072019-04-15 16:36:00 -0600198 def testInsideServiceOutsideMethodOutsideChroot(self):
199 """Test the outside method override works as expected."""
200 self.PatchObject(self.router, '_GetMethod',
201 return_value=self._mock_callable(expect_called=True))
202 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
203 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700204 'InsideServiceOutsideMethod', self.api_config,
205 self.binary_input_handler, [self.binary_output_handler],
206 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600207
208 def testInsideServiceInsideMethodOutsideChroot(self):
209 """Test calling an inside method from outside the chroot."""
210 self.PatchObject(self.router, '_GetMethod',
211 return_value=self._mock_callable(expect_called=False))
212 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600213
214 service = 'chromite.api.InsideChrootApiService'
215 method = 'InsideServiceInsideMethod'
216 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700217 self.router.Route(service, method, self.api_config,
218 self.binary_input_handler, [self.binary_output_handler],
219 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600220
Alex Kleinc05f3d12019-05-29 14:16:21 -0600221 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Klein00aa8072019-04-15 16:36:00 -0600222
223 def testInsideServiceOutsideMethodInsideChroot(self):
224 """Test inside chroot for outside method raises an error."""
225 self.PatchObject(self.router, '_GetMethod',
226 return_value=self._mock_callable(expect_called=False))
227 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700228 with self.assertRaises(cros_build_lib.DieSystemExit):
229 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700230 'InsideServiceOutsideMethod', self.api_config,
231 self.binary_input_handler, [self.binary_output_handler],
232 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700233
Alex Klein00aa8072019-04-15 16:36:00 -0600234 def testOutsideServiceOutsideMethodOutsideChroot(self):
235 """Test outside/outside/outside works correctly."""
236 self.PatchObject(self.router, '_GetMethod',
237 return_value=self._mock_callable(expect_called=True))
238 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein2bfacb22019-02-04 11:42:17 -0700239 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700240 'OutsideServiceOutsideMethod', self.api_config,
241 self.binary_input_handler, [self.binary_output_handler],
242 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700243
Alex Klein00aa8072019-04-15 16:36:00 -0600244 def testOutsideServiceInsideMethodInsideChroot(self):
245 """Test the inside method assertion override works properly."""
246 self.PatchObject(self.router, '_GetMethod',
247 return_value=self._mock_callable(expect_called=True))
248 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700249 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700250 'OutsideServiceInsideMethod', self.api_config,
251 self.binary_input_handler, [self.binary_output_handler],
252 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600253
254 def testOutsideServiceInsideMethodOutsideChroot(self):
255 """Test calling an inside override method from outside the chroot."""
256 self.PatchObject(self.router, '_GetMethod',
257 return_value=self._mock_callable(expect_called=False))
258 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600259
260 service = 'chromite.api.OutsideChrootApiService'
261 method = 'OutsideServiceInsideMethod'
262 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700263 self.router.Route(service, method, self.api_config,
264 self.binary_input_handler, [self.binary_output_handler],
265 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600266
Alex Kleinc05f3d12019-05-29 14:16:21 -0600267 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600268
269 def testReexecNonemptyOutput(self):
270 """Test calling an inside chroot method that produced output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600271 self.PatchObject(self.router, '_GetMethod',
272 return_value=self._mock_callable(expect_called=False))
273 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Kleinc7d647f2020-01-06 12:00:48 -0700274
275 # Patch the chroot tempdir method to return a tempdir with our subprocess
276 # tempdir so the output file will be in the expected location.
277 tempdir = osutils.TempDir()
278 original = tempdir.tempdir
279 tempdir.tempdir = self.subprocess_tempdir
280 self.PatchObject(chroot_lib.Chroot, 'tempdir', return_value=tempdir)
281
Alex Kleine191ed62020-02-27 15:59:55 -0700282 expected_output_msg = build_api_test_pb2.TestResultMessage()
283 expected_output_msg.result = 'foo'
284
285 # Set the command side effect to write out our expected output to the
286 # output file for the inside the chroot reexecution of the endpoint.
287 # This lets us make sure the logic moving everything out works as intended.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600288 self.rc.SetDefaultCmdResult(
Alex Kleine191ed62020-02-27 15:59:55 -0700289 side_effect=self._writeChrootCallOutput(
290 content=expected_output_msg.SerializeToString(), mode='wb'))
Alex Kleinbd6edf82019-07-18 10:30:49 -0600291
292 service = 'chromite.api.OutsideChrootApiService'
293 method = 'OutsideServiceInsideMethod'
294 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700295 self.router.Route(service, method, self.api_config,
296 self.binary_input_handler, [self.binary_output_handler],
297 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600298
299 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
300
301 # It should be writing the result out to our output file.
Alex Kleine191ed62020-02-27 15:59:55 -0700302 output_msg = build_api_test_pb2.TestResultMessage()
303 self.binary_output_handler.read_into(output_msg)
304 self.assertEqual(expected_output_msg, output_msg)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600305
Alex Kleinc7d647f2020-01-06 12:00:48 -0700306 tempdir.tempdir = original
307 del tempdir
308
Alex Kleinbd6edf82019-07-18 10:30:49 -0600309 def testReexecEmptyOutput(self):
310 """Test calling an inside chroot method that produced no output."""
Alex Kleine191ed62020-02-27 15:59:55 -0700311 self.PatchObject(self.router, '_GetMethod',
312 return_value=self._mock_callable(expect_called=False))
313 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
314 expected_output_msg = build_api_test_pb2.TestResultMessage()
315
316 # Set the command side effect to write out our expected output to the
317 # output file for the inside the chroot reexecution of the endpoint.
318 # This lets us make sure the logic moving everything out works as intended.
319 self.rc.SetDefaultCmdResult(
320 side_effect=self._writeChrootCallOutput(
321 content=expected_output_msg.SerializeToString(), mode='wb'))
322
323 service = 'chromite.api.OutsideChrootApiService'
324 method = 'OutsideServiceInsideMethod'
325 service_method = '%s/%s' % (service, method)
326 self.router.Route(service, method, self.api_config,
327 self.binary_input_handler, [self.binary_output_handler],
328 self.binary_config_handler)
329
330 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
331
332 output_msg = build_api_test_pb2.TestResultMessage()
333 self.binary_output_handler.read_into(output_msg)
334 self.assertEqual(expected_output_msg, output_msg)
335
336 def testReexecNoOutput(self):
337 """Test calling an inside chroot method that produced no output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600338 self.PatchObject(self.router, '_GetMethod',
339 return_value=self._mock_callable(expect_called=False))
340 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
341 self.rc.SetDefaultCmdResult(returncode=1)
342
343 service = 'chromite.api.OutsideChrootApiService'
344 method = 'OutsideServiceInsideMethod'
345 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700346 self.router.Route(service, method, self.api_config,
347 self.binary_input_handler, [self.binary_output_handler],
348 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600349
350 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleine191ed62020-02-27 15:59:55 -0700351
352 output_msg = build_api_test_pb2.TestResultMessage()
353 empty_msg = build_api_test_pb2.TestResultMessage()
354 self.binary_output_handler.read_into(output_msg)
355 self.assertEqual(empty_msg, output_msg)
Alex Klein6431d3a2019-08-29 10:16:08 -0600356
357 def testInvalidService(self):
358 """Test invalid service call."""
359 service = 'chromite.api.DoesNotExist'
360 method = 'OutsideServiceInsideMethod'
361
362 with self.assertRaises(router.UnknownServiceError):
Alex Kleine191ed62020-02-27 15:59:55 -0700363 self.router.Route(service, method, self.api_config,
364 self.binary_input_handler, [self.binary_output_handler],
365 self.binary_config_handler)
Alex Klein6431d3a2019-08-29 10:16:08 -0600366
367 def testInvalidMethod(self):
368 """Test invalid method call."""
369 service = 'chromite.api.OutsideChrootApiService'
370 method = 'DoesNotExist'
371
372 with self.assertRaises(router.UnknownMethodError):
Alex Kleine191ed62020-02-27 15:59:55 -0700373 self.router.Route(service, method, self.api_config,
374 self.binary_input_handler, [self.binary_output_handler],
375 self.binary_config_handler)
Alex Klein6cce6f62021-03-02 14:24:05 -0700376
377 def testListVisibility(self):
378 """Test visibility options."""
379 service = 'HiddenService'
380 method = 'HiddenMethod'
381
382 for current in self.router.ListMethods():
383 self.assertNotIn(service, current)
384 self.assertNotIn(method, current)