blob: 69bfccd8460af34dd562c5468e0282d307e215e0 [file] [log] [blame]
Alex Kleinf4dc4f52018-12-05 13:55:12 -07001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Alex Klein2bfacb22019-02-04 11:42:17 -07006"""Tests for the build_api script covering the base Build API functionality."""
7
Alex Kleinf4dc4f52018-12-05 13:55:12 -07008from __future__ import print_function
9
Alex Klein5bcb4d22019-03-21 13:51:54 -060010import os
Mike Frysingeref94e4c2020-02-10 23:59:54 -050011import sys
Alex Klein5bcb4d22019-03-21 13:51:54 -060012
Alex Kleinbd6edf82019-07-18 10:30:49 -060013from google.protobuf import json_format
14
Alex Klein69339cc2019-07-22 14:08:35 -060015from chromite.api import api_config
Alex Kleine191ed62020-02-27 15:59:55 -070016from chromite.api import message_util
Alex Klein146d4772019-06-20 13:48:25 -060017from chromite.api import router
Alex Klein7107bdd2019-03-14 17:14:31 -060018from chromite.api.gen.chromite.api import build_api_test_pb2
Alex Kleinc7d647f2020-01-06 12:00:48 -070019from chromite.lib import chroot_lib
Alex Klein2bfacb22019-02-04 11:42:17 -070020from chromite.lib import cros_build_lib
Alex Kleinf4dc4f52018-12-05 13:55:12 -070021from chromite.lib import cros_test_lib
Alex Klein5bcb4d22019-03-21 13:51:54 -060022from chromite.lib import osutils
Alex Kleinf4dc4f52018-12-05 13:55:12 -070023
24
Mike Frysingeref94e4c2020-02-10 23:59:54 -050025assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
26
27
Alex Klein69339cc2019-07-22 14:08:35 -060028class RouterTest(cros_test_lib.RunCommandTempDirTestCase,
29 api_config.ApiConfigMixin):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070030 """Test Router functionality."""
Alex Kleinf4dc4f52018-12-05 13:55:12 -070031
32 def setUp(self):
Alex Klein146d4772019-06-20 13:48:25 -060033 self.router = router.Router()
Alex Kleinf4dc4f52018-12-05 13:55:12 -070034 self.router.Register(build_api_test_pb2)
35
Alex Klein00aa8072019-04-15 16:36:00 -060036 self.chroot_dir = os.path.join(self.tempdir, 'chroot')
37 chroot_tmp = os.path.join(self.chroot_dir, 'tmp')
38 # Make the tmp dir for the re-exec inside chroot input/output files.
39 osutils.SafeMakedirs(chroot_tmp)
40
Alex Kleine191ed62020-02-27 15:59:55 -070041 # Build the input/output/config paths we'll be using in the tests.
42 self.json_input_file = os.path.join(self.tempdir, 'input.json')
43 self.json_output_file = os.path.join(self.tempdir, 'output.json')
44 self.json_config_file = os.path.join(self.tempdir, 'config.json')
45 self.binary_input_file = os.path.join(self.tempdir, 'input.bin')
46 self.binary_output_file = os.path.join(self.tempdir, 'output.bin')
47 self.binary_config_file = os.path.join(self.tempdir, 'config.bin')
48
49 # The message handlers for the respective files.
50 self.json_input_handler = message_util.get_message_handler(
51 self.json_input_file, message_util.FORMAT_JSON)
52 self.json_output_handler = message_util.get_message_handler(
53 self.json_output_file, message_util.FORMAT_JSON)
54 self.json_config_handler = message_util.get_message_handler(
55 self.json_config_file, message_util.FORMAT_JSON)
56 self.binary_input_handler = message_util.get_message_handler(
57 self.binary_input_file, message_util.FORMAT_BINARY)
58 self.binary_output_handler = message_util.get_message_handler(
59 self.binary_output_file, message_util.FORMAT_BINARY)
60 self.binary_config_handler = message_util.get_message_handler(
61 self.binary_config_file, message_util.FORMAT_BINARY)
62
63 # Build an input message to use.
64 self.expected_id = 'input id'
65 input_msg = build_api_test_pb2.TestRequestMessage()
66 input_msg.id = self.expected_id
67 input_msg.chroot.path = self.chroot_dir
68
69 # Write out base input and config messages.
70 osutils.WriteFile(self.json_input_file,
71 json_format.MessageToJson(input_msg))
72 osutils.WriteFile(
73 self.binary_input_file, input_msg.SerializeToString(), mode='wb')
74
75 config_msg = self.api_config.get_proto()
76 osutils.WriteFile(self.json_config_file,
77 json_format.MessageToJson(config_msg))
78 osutils.WriteFile(
79 self.binary_config_file, config_msg.SerializeToString(), mode='wb')
Alex Kleinbd6edf82019-07-18 10:30:49 -060080
81 self.subprocess_tempdir = os.path.join(self.chroot_dir, 'tempdir')
82 osutils.SafeMakedirs(self.subprocess_tempdir)
Alex Klein5bcb4d22019-03-21 13:51:54 -060083
Alex Kleine191ed62020-02-27 15:59:55 -070084 def testJsonInputOutputMethod(self):
85 """Test json input/output handling."""
Alex Klein69339cc2019-07-22 14:08:35 -060086 def impl(input_msg, output_msg, config):
Alex Kleinf4dc4f52018-12-05 13:55:12 -070087 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
88 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
Alex Klein69339cc2019-07-22 14:08:35 -060089 self.assertIsInstance(config, api_config.ApiConfig)
Alex Kleine191ed62020-02-27 15:59:55 -070090 self.assertEqual(config, self.api_config)
Alex Kleinf4dc4f52018-12-05 13:55:12 -070091
92 self.PatchObject(self.router, '_GetMethod', return_value=impl)
93
Alex Kleine191ed62020-02-27 15:59:55 -070094 self.router.Route(
95 'chromite.api.TestApiService',
96 'InputOutputMethod',
97 self.api_config,
98 self.json_input_handler,
99 [self.json_output_handler],
100 self.json_config_handler)
101
102 def testBinaryInputOutputMethod(self):
103 """Test binary input/output handling."""
104 def impl(input_msg, output_msg, config):
105 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
106 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
107 self.assertIsInstance(config, api_config.ApiConfig)
108 self.assertEqual(config, self.api_config)
109
110 self.PatchObject(self.router, '_GetMethod', return_value=impl)
111
112 self.router.Route(
113 'chromite.api.TestApiService',
114 'InputOutputMethod',
115 self.api_config,
116 self.binary_input_handler,
117 [self.binary_output_handler],
118 self.binary_config_handler)
119
120 def testMultipleOutputHandling(self):
121 """Test multiple output handling."""
122 expected_result = 'Success!'
123
124 def impl(input_msg, output_msg, config):
125 self.assertIsInstance(input_msg, build_api_test_pb2.TestRequestMessage)
126 self.assertIsInstance(output_msg, build_api_test_pb2.TestResultMessage)
127 self.assertIsInstance(config, api_config.ApiConfig)
128 self.assertEqual(config, self.api_config)
129 # Set the property on the output to test against.
130 output_msg.result = expected_result
131
132 self.PatchObject(self.router, '_GetMethod', return_value=impl)
133
134 self.router.Route(
135 'chromite.api.TestApiService',
136 'InputOutputMethod',
137 self.api_config,
138 self.binary_input_handler,
139 [self.binary_output_handler, self.json_output_handler],
140 self.binary_config_handler)
141
142 # Make sure it did write out all the expected files.
143 self.assertExists(self.binary_output_file)
144 self.assertExists(self.json_output_file)
145
146 # Parse the output files back into a message.
147 binary_msg = build_api_test_pb2.TestResultMessage()
148 json_msg = build_api_test_pb2.TestResultMessage()
149 self.binary_output_handler.read_into(binary_msg)
150 self.json_output_handler.read_into(json_msg)
151
152 # Make sure the parsed messages have the expected content.
153 self.assertEqual(binary_msg.result, expected_result)
154 self.assertEqual(json_msg.result, expected_result)
Alex Kleinf4dc4f52018-12-05 13:55:12 -0700155
Alex Klein2bfacb22019-02-04 11:42:17 -0700156 def testRenameMethod(self):
157 """Test implementation name config."""
158 def _GetMethod(_, method_name):
159 self.assertEqual('CorrectName', method_name)
Alex Klein69339cc2019-07-22 14:08:35 -0600160 return lambda x, y, z: None
Alex Klein2bfacb22019-02-04 11:42:17 -0700161
162 self.PatchObject(self.router, '_GetMethod', side_effect=_GetMethod)
163
164 self.router.Route('chromite.api.TestApiService', 'RenamedMethod',
Alex Kleine191ed62020-02-27 15:59:55 -0700165 self.api_config, self.binary_input_handler,
166 [self.binary_output_handler], self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700167
Alex Klein00aa8072019-04-15 16:36:00 -0600168 def _mock_callable(self, expect_called):
169 """Helper to create the implementation mock to test chroot assertions.
170
171 Args:
172 expect_called (bool): Whether the implementation should be called.
173 When False, an assertion will fail if it is called.
174
175 Returns:
176 callable - The implementation.
177 """
Alex Klein69339cc2019-07-22 14:08:35 -0600178 def impl(_input_msg, _output_msg, _config):
Alex Klein00aa8072019-04-15 16:36:00 -0600179 self.assertTrue(expect_called,
Alex Klein2bfacb22019-02-04 11:42:17 -0700180 'The implementation should not have been called.')
Alex Klein2bfacb22019-02-04 11:42:17 -0700181
Alex Klein00aa8072019-04-15 16:36:00 -0600182 return impl
Alex Klein2bfacb22019-02-04 11:42:17 -0700183
Alex Kleine191ed62020-02-27 15:59:55 -0700184 def _writeChrootCallOutput(self, content='{}', mode='w'):
Alex Kleinbd6edf82019-07-18 10:30:49 -0600185 def impl(*_args, **_kwargs):
186 """Side effect for inside-chroot calls to the API."""
187 osutils.WriteFile(
188 os.path.join(self.subprocess_tempdir,
189 router.Router.REEXEC_OUTPUT_FILE),
Alex Kleine191ed62020-02-27 15:59:55 -0700190 content,
191 mode=mode)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600192
193 return impl
194
Alex Klein00aa8072019-04-15 16:36:00 -0600195 def testInsideServiceInsideMethodInsideChroot(self):
196 """Test inside/inside/inside works correctly."""
197 self.PatchObject(self.router, '_GetMethod',
198 return_value=self._mock_callable(expect_called=True))
199 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700200 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700201 'InsideServiceInsideMethod', self.api_config,
202 self.binary_input_handler, [self.binary_output_handler],
203 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700204
Alex Klein00aa8072019-04-15 16:36:00 -0600205 def testInsideServiceOutsideMethodOutsideChroot(self):
206 """Test the outside method override works as expected."""
207 self.PatchObject(self.router, '_GetMethod',
208 return_value=self._mock_callable(expect_called=True))
209 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
210 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700211 'InsideServiceOutsideMethod', self.api_config,
212 self.binary_input_handler, [self.binary_output_handler],
213 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600214
215 def testInsideServiceInsideMethodOutsideChroot(self):
216 """Test calling an inside method from outside the chroot."""
217 self.PatchObject(self.router, '_GetMethod',
218 return_value=self._mock_callable(expect_called=False))
219 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600220
221 service = 'chromite.api.InsideChrootApiService'
222 method = 'InsideServiceInsideMethod'
223 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700224 self.router.Route(service, method, self.api_config,
225 self.binary_input_handler, [self.binary_output_handler],
226 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600227
Alex Kleinc05f3d12019-05-29 14:16:21 -0600228 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Klein00aa8072019-04-15 16:36:00 -0600229
230 def testInsideServiceOutsideMethodInsideChroot(self):
231 """Test inside chroot for outside method raises an error."""
232 self.PatchObject(self.router, '_GetMethod',
233 return_value=self._mock_callable(expect_called=False))
234 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700235 with self.assertRaises(cros_build_lib.DieSystemExit):
236 self.router.Route('chromite.api.InsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700237 'InsideServiceOutsideMethod', self.api_config,
238 self.binary_input_handler, [self.binary_output_handler],
239 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700240
Alex Klein00aa8072019-04-15 16:36:00 -0600241 def testOutsideServiceOutsideMethodOutsideChroot(self):
242 """Test outside/outside/outside works correctly."""
243 self.PatchObject(self.router, '_GetMethod',
244 return_value=self._mock_callable(expect_called=True))
245 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein2bfacb22019-02-04 11:42:17 -0700246 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700247 'OutsideServiceOutsideMethod', self.api_config,
248 self.binary_input_handler, [self.binary_output_handler],
249 self.binary_config_handler)
Alex Klein2bfacb22019-02-04 11:42:17 -0700250
Alex Klein00aa8072019-04-15 16:36:00 -0600251 def testOutsideServiceInsideMethodInsideChroot(self):
252 """Test the inside method assertion override works properly."""
253 self.PatchObject(self.router, '_GetMethod',
254 return_value=self._mock_callable(expect_called=True))
255 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=True)
Alex Klein2bfacb22019-02-04 11:42:17 -0700256 self.router.Route('chromite.api.OutsideChrootApiService',
Alex Kleine191ed62020-02-27 15:59:55 -0700257 'OutsideServiceInsideMethod', self.api_config,
258 self.binary_input_handler, [self.binary_output_handler],
259 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600260
261 def testOutsideServiceInsideMethodOutsideChroot(self):
262 """Test calling an inside override method from outside the chroot."""
263 self.PatchObject(self.router, '_GetMethod',
264 return_value=self._mock_callable(expect_called=False))
265 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Klein00aa8072019-04-15 16:36:00 -0600266
267 service = 'chromite.api.OutsideChrootApiService'
268 method = 'OutsideServiceInsideMethod'
269 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700270 self.router.Route(service, method, self.api_config,
271 self.binary_input_handler, [self.binary_output_handler],
272 self.binary_config_handler)
Alex Klein00aa8072019-04-15 16:36:00 -0600273
Alex Kleinc05f3d12019-05-29 14:16:21 -0600274 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600275
276 def testReexecNonemptyOutput(self):
277 """Test calling an inside chroot method that produced output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600278 self.PatchObject(self.router, '_GetMethod',
279 return_value=self._mock_callable(expect_called=False))
280 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
Alex Kleinc7d647f2020-01-06 12:00:48 -0700281
282 # Patch the chroot tempdir method to return a tempdir with our subprocess
283 # tempdir so the output file will be in the expected location.
284 tempdir = osutils.TempDir()
285 original = tempdir.tempdir
286 tempdir.tempdir = self.subprocess_tempdir
287 self.PatchObject(chroot_lib.Chroot, 'tempdir', return_value=tempdir)
288
Alex Kleine191ed62020-02-27 15:59:55 -0700289 expected_output_msg = build_api_test_pb2.TestResultMessage()
290 expected_output_msg.result = 'foo'
291
292 # Set the command side effect to write out our expected output to the
293 # output file for the inside the chroot reexecution of the endpoint.
294 # This lets us make sure the logic moving everything out works as intended.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600295 self.rc.SetDefaultCmdResult(
Alex Kleine191ed62020-02-27 15:59:55 -0700296 side_effect=self._writeChrootCallOutput(
297 content=expected_output_msg.SerializeToString(), mode='wb'))
Alex Kleinbd6edf82019-07-18 10:30:49 -0600298
299 service = 'chromite.api.OutsideChrootApiService'
300 method = 'OutsideServiceInsideMethod'
301 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700302 self.router.Route(service, method, self.api_config,
303 self.binary_input_handler, [self.binary_output_handler],
304 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600305
306 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
307
308 # It should be writing the result out to our output file.
Alex Kleine191ed62020-02-27 15:59:55 -0700309 output_msg = build_api_test_pb2.TestResultMessage()
310 self.binary_output_handler.read_into(output_msg)
311 self.assertEqual(expected_output_msg, output_msg)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600312
Alex Kleinc7d647f2020-01-06 12:00:48 -0700313 tempdir.tempdir = original
314 del tempdir
315
Alex Kleinbd6edf82019-07-18 10:30:49 -0600316 def testReexecEmptyOutput(self):
317 """Test calling an inside chroot method that produced no output."""
Alex Kleine191ed62020-02-27 15:59:55 -0700318 self.PatchObject(self.router, '_GetMethod',
319 return_value=self._mock_callable(expect_called=False))
320 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
321 expected_output_msg = build_api_test_pb2.TestResultMessage()
322
323 # Set the command side effect to write out our expected output to the
324 # output file for the inside the chroot reexecution of the endpoint.
325 # This lets us make sure the logic moving everything out works as intended.
326 self.rc.SetDefaultCmdResult(
327 side_effect=self._writeChrootCallOutput(
328 content=expected_output_msg.SerializeToString(), mode='wb'))
329
330 service = 'chromite.api.OutsideChrootApiService'
331 method = 'OutsideServiceInsideMethod'
332 service_method = '%s/%s' % (service, method)
333 self.router.Route(service, method, self.api_config,
334 self.binary_input_handler, [self.binary_output_handler],
335 self.binary_config_handler)
336
337 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
338
339 output_msg = build_api_test_pb2.TestResultMessage()
340 self.binary_output_handler.read_into(output_msg)
341 self.assertEqual(expected_output_msg, output_msg)
342
343 def testReexecNoOutput(self):
344 """Test calling an inside chroot method that produced no output."""
Alex Kleinbd6edf82019-07-18 10:30:49 -0600345 self.PatchObject(self.router, '_GetMethod',
346 return_value=self._mock_callable(expect_called=False))
347 self.PatchObject(cros_build_lib, 'IsInsideChroot', return_value=False)
348 self.rc.SetDefaultCmdResult(returncode=1)
349
350 service = 'chromite.api.OutsideChrootApiService'
351 method = 'OutsideServiceInsideMethod'
352 service_method = '%s/%s' % (service, method)
Alex Kleine191ed62020-02-27 15:59:55 -0700353 self.router.Route(service, method, self.api_config,
354 self.binary_input_handler, [self.binary_output_handler],
355 self.binary_config_handler)
Alex Kleinbd6edf82019-07-18 10:30:49 -0600356
357 self.assertCommandContains(['build_api', service_method], enter_chroot=True)
Alex Kleine191ed62020-02-27 15:59:55 -0700358
359 output_msg = build_api_test_pb2.TestResultMessage()
360 empty_msg = build_api_test_pb2.TestResultMessage()
361 self.binary_output_handler.read_into(output_msg)
362 self.assertEqual(empty_msg, output_msg)
Alex Klein6431d3a2019-08-29 10:16:08 -0600363
364 def testInvalidService(self):
365 """Test invalid service call."""
366 service = 'chromite.api.DoesNotExist'
367 method = 'OutsideServiceInsideMethod'
368
369 with self.assertRaises(router.UnknownServiceError):
Alex Kleine191ed62020-02-27 15:59:55 -0700370 self.router.Route(service, method, self.api_config,
371 self.binary_input_handler, [self.binary_output_handler],
372 self.binary_config_handler)
Alex Klein6431d3a2019-08-29 10:16:08 -0600373
374 def testInvalidMethod(self):
375 """Test invalid method call."""
376 service = 'chromite.api.OutsideChrootApiService'
377 method = 'DoesNotExist'
378
379 with self.assertRaises(router.UnknownMethodError):
Alex Kleine191ed62020-02-27 15:59:55 -0700380 self.router.Route(service, method, self.api_config,
381 self.binary_input_handler, [self.binary_output_handler],
382 self.binary_config_handler)
Alex Klein6cce6f62021-03-02 14:24:05 -0700383
384 def testListVisibility(self):
385 """Test visibility options."""
386 service = 'HiddenService'
387 method = 'HiddenMethod'
388
389 for current in self.router.ListMethods():
390 self.assertNotIn(service, current)
391 self.assertNotIn(method, current)