blob: 32761abd58f5ccc8bbdb211e573c1c27c58e058d [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Alex Klein146d4772019-06-20 13:48:25 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Alex Kleine0fa6422019-06-21 12:01:39 -06005"""Router class for the Build API.
6
7Handles routing requests to the appropriate controller and handles service
8registration.
9"""
Alex Klein146d4772019-06-20 13:48:25 -060010
Alex Klein92341cd2020-02-27 14:11:04 -070011import collections
Mike Frysingerc2e72b42023-03-03 22:34:04 -050012import contextlib
Alex Klein146d4772019-06-20 13:48:25 -060013import importlib
Chris McDonald1672ddb2021-07-21 11:48:23 -060014import logging
Alex Klein146d4772019-06-20 13:48:25 -060015import os
Tomasz Tylendab4292302021-08-08 18:59:36 +090016from types import ModuleType
17from typing import Callable, List, TYPE_CHECKING
Alex Klein146d4772019-06-20 13:48:25 -060018
Mike Frysinger2c024062021-05-22 15:43:22 -040019from chromite.third_party.google.protobuf import symbol_database
Alex Klein146d4772019-06-20 13:48:25 -060020
21from chromite.api import controller
22from chromite.api import field_handler
Alex Klein4de25e82019-08-05 15:58:39 -060023from chromite.api.gen.chromite.api import android_pb2
Alex Klein54e38e32019-06-21 14:54:17 -060024from chromite.api.gen.chromite.api import api_pb2
Alex Klein146d4772019-06-20 13:48:25 -060025from chromite.api.gen.chromite.api import artifacts_pb2
26from chromite.api.gen.chromite.api import binhost_pb2
27from chromite.api.gen.chromite.api import build_api_pb2
Tristan Honscheid52ba4d22023-02-09 11:59:29 -070028from chromite.api.gen.chromite.api import copybot_pb2
Alex Klein146d4772019-06-20 13:48:25 -060029from chromite.api.gen.chromite.api import depgraph_pb2
Benjamin Shai80317fc2023-08-22 19:33:41 +000030from chromite.api.gen.chromite.api import dlc_pb2
Jett Rink17ed0f52020-09-25 17:14:31 -060031from chromite.api.gen.chromite.api import firmware_pb2
Alex Klein146d4772019-06-20 13:48:25 -060032from chromite.api.gen.chromite.api import image_pb2
Alex Kleind4d9caa2021-11-10 15:44:52 -070033from chromite.api.gen.chromite.api import metadata_pb2
Lizzy Preslande723c012022-06-10 05:05:37 +000034from chromite.api.gen.chromite.api import observability_pb2
Alex Kleineb77ffa2019-05-28 14:47:44 -060035from chromite.api.gen.chromite.api import packages_pb2
George Engelbrechtfe63c8c2019-08-31 22:51:29 -060036from chromite.api.gen.chromite.api import payload_pb2
Alexander Liu008389c2022-06-27 18:30:11 +000037from chromite.api.gen.chromite.api import portage_explorer_pb2
Alex Klein146d4772019-06-20 13:48:25 -060038from chromite.api.gen.chromite.api import sdk_pb2
39from chromite.api.gen.chromite.api import sysroot_pb2
40from chromite.api.gen.chromite.api import test_pb2
Tiancong Wangaf050172019-07-10 11:52:03 -070041from chromite.api.gen.chromite.api import toolchain_pb2
Alex Klein12fc7ca2023-04-26 18:23:18 -060042from chromite.lib import constants
Alex Klein146d4772019-06-20 13:48:25 -060043from chromite.lib import cros_build_lib
Alex Klein146d4772019-06-20 13:48:25 -060044from chromite.lib import osutils
Alex Klein92341cd2020-02-27 14:11:04 -070045from chromite.utils import memoize
Alex Klein146d4772019-06-20 13:48:25 -060046
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040047
Tomasz Tylendab4292302021-08-08 18:59:36 +090048if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060049 from chromite.third_party import google
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040050
Alex Klein1699fab2022-09-08 08:46:06 -060051 from chromite.api import api_config
52 from chromite.api import message_util
Mike Frysinger88770ef2021-05-21 11:04:00 -040053
Alex Klein92341cd2020-02-27 14:11:04 -070054MethodData = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -060055 "MethodData", ("service_descriptor", "module_name", "method_descriptor")
56)
Alex Klein146d4772019-06-20 13:48:25 -060057
Mike Frysingeref94e4c2020-02-10 23:59:54 -050058
Alex Klein146d4772019-06-20 13:48:25 -060059class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060060 """Base error class for the module."""
Alex Klein146d4772019-06-20 13:48:25 -060061
62
Alex Kleind3394c22020-06-16 14:05:06 -060063class InvalidSdkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060064 """Raised when the SDK is invalid or does not exist."""
Alex Kleind3394c22020-06-16 14:05:06 -060065
66
Alex Klein146d4772019-06-20 13:48:25 -060067class CrosSdkNotRunError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060068 """Raised when the cros_sdk command could not be run to enter the chroot."""
Alex Klein146d4772019-06-20 13:48:25 -060069
70
71# API Service Errors.
72class UnknownServiceError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060073 """Error raised when the requested service has not been registered."""
Alex Klein146d4772019-06-20 13:48:25 -060074
75
76class ControllerModuleNotDefinedError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060077 """Error class for when no controller has been defined for a service."""
Alex Klein146d4772019-06-20 13:48:25 -060078
79
80class ServiceControllerNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060081 """Error raised when the service's controller cannot be imported."""
Alex Klein146d4772019-06-20 13:48:25 -060082
83
Alex Klein12fc7ca2023-04-26 18:23:18 -060084class TotSdkError(Error):
85 """When attempting to run a ToT endpoint inside the SDK."""
86
87
Alex Klein146d4772019-06-20 13:48:25 -060088# API Method Errors.
89class UnknownMethodError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060090 """The service has been defined in the proto, but the method has not."""
Alex Klein146d4772019-06-20 13:48:25 -060091
92
93class MethodNotFoundError(Error):
Alex Klein54c891a2023-01-24 10:45:41 -070094 """The method's implementation cannot be found in the controller."""
Alex Klein146d4772019-06-20 13:48:25 -060095
96
Alex Klein074f94f2023-06-22 10:32:06 -060097class Router:
Alex Klein1699fab2022-09-08 08:46:06 -060098 """Encapsulates the request dispatching logic."""
Alex Klein146d4772019-06-20 13:48:25 -060099
Alex Klein1699fab2022-09-08 08:46:06 -0600100 REEXEC_INPUT_FILE = "input_proto"
101 REEXEC_OUTPUT_FILE = "output_proto"
102 REEXEC_CONFIG_FILE = "config_proto"
Alex Kleinbd6edf82019-07-18 10:30:49 -0600103
Alex Klein1699fab2022-09-08 08:46:06 -0600104 def __init__(self):
105 self._services = {}
106 self._aliases = {}
107 # All imported generated messages get added to this symbol db.
108 self._sym_db = symbol_database.Default()
Alex Klein146d4772019-06-20 13:48:25 -0600109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 # Save the service and method extension info for looking up
111 # configured extension data.
112 extensions = build_api_pb2.DESCRIPTOR.extensions_by_name
113 self._svc_options_ext = extensions["service_options"]
114 self._method_options_ext = extensions["method_options"]
Alex Klein92341cd2020-02-27 14:11:04 -0700115
Alex Klein1699fab2022-09-08 08:46:06 -0600116 @memoize.Memoize
117 def _get_method_data(self, service_name, method_name):
118 """Get the descriptors and module name for the given Service/Method."""
119 try:
120 svc, module_name = self._services[service_name]
121 except KeyError:
122 raise UnknownServiceError(
123 "The %s service has not been registered." % service_name
124 )
Alex Klein92341cd2020-02-27 14:11:04 -0700125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 try:
127 method_desc = svc.methods_by_name[method_name]
128 except KeyError:
129 raise UnknownMethodError(
130 "The %s method has not been defined in the %s "
131 "service." % (method_name, service_name)
132 )
Alex Klein92341cd2020-02-27 14:11:04 -0700133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 return MethodData(
135 service_descriptor=svc,
136 module_name=module_name,
137 method_descriptor=method_desc,
138 )
Alex Klein92341cd2020-02-27 14:11:04 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 def get_input_message_instance(self, service_name, method_name):
141 """Get an empty input message instance for the specified method."""
142 method_data = self._get_method_data(service_name, method_name)
143 return self._sym_db.GetPrototype(
144 method_data.method_descriptor.input_type
145 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700146
Alex Klein1699fab2022-09-08 08:46:06 -0600147 def _get_output_message_instance(self, service_name, method_name):
148 """Get an empty output message instance for the specified method."""
149 method_data = self._get_method_data(service_name, method_name)
150 return self._sym_db.GetPrototype(
151 method_data.method_descriptor.output_type
152 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 def _get_module_name(self, service_name, method_name):
155 """Get the name of the module containing the endpoint implementation."""
156 return self._get_method_data(service_name, method_name).module_name
Alex Klein92341cd2020-02-27 14:11:04 -0700157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 def _get_service_options(self, service_name, method_name):
159 """Get the configured service options for the endpoint."""
160 method_data = self._get_method_data(service_name, method_name)
161 svc_extensions = method_data.service_descriptor.GetOptions().Extensions
162 return svc_extensions[self._svc_options_ext]
Alex Klein92341cd2020-02-27 14:11:04 -0700163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 def _get_method_options(self, service_name, method_name):
165 """Get the configured method options for the endpoint."""
166 method_data = self._get_method_data(service_name, method_name)
167 method_extensions = (
168 method_data.method_descriptor.GetOptions().Extensions
169 )
170 return method_extensions[self._method_options_ext]
Alex Klein146d4772019-06-20 13:48:25 -0600171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 def Register(self, proto_module: ModuleType):
173 """Register the services from a generated proto module.
Alex Klein146d4772019-06-20 13:48:25 -0600174
Alex Klein1699fab2022-09-08 08:46:06 -0600175 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600176 proto_module: The generated proto module to register.
Alex Klein146d4772019-06-20 13:48:25 -0600177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600179 ServiceModuleNotDefinedError when the service cannot be found in the
180 provided module.
Alex Klein1699fab2022-09-08 08:46:06 -0600181 """
182 services = proto_module.DESCRIPTOR.services_by_name
183 for service_name, svc in services.items():
184 module_name = (
185 svc.GetOptions().Extensions[self._svc_options_ext].module
186 )
Alex Klein146d4772019-06-20 13:48:25 -0600187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 if not module_name:
189 raise ControllerModuleNotDefinedError(
Alex Klein54c891a2023-01-24 10:45:41 -0700190 "The module must be defined in the service definition: "
191 f"{proto_module}.{service_name}"
Alex Klein1699fab2022-09-08 08:46:06 -0600192 )
Alex Klein146d4772019-06-20 13:48:25 -0600193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 self._services[svc.full_name] = (svc, module_name)
Alex Klein146d4772019-06-20 13:48:25 -0600195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 def ListMethods(self):
197 """List all methods registered with the router."""
198 services = []
199 for service_name, (svc, _module) in self._services.items():
200 svc_visibility = getattr(
201 svc.GetOptions().Extensions[self._svc_options_ext],
202 "service_visibility",
203 build_api_pb2.LV_VISIBLE,
204 )
205 if svc_visibility == build_api_pb2.LV_HIDDEN:
206 continue
Alex Klein6cce6f62021-03-02 14:24:05 -0700207
Alex Klein1699fab2022-09-08 08:46:06 -0600208 for method_name in svc.methods_by_name.keys():
209 method_options = self._get_method_options(
210 service_name, method_name
211 )
212 method_visibility = getattr(
213 method_options,
214 "method_visibility",
215 build_api_pb2.LV_VISIBLE,
216 )
217 if method_visibility == build_api_pb2.LV_HIDDEN:
218 continue
219
220 services.append("%s/%s" % (service_name, method_name))
221
222 return sorted(services)
223
224 def Route(
225 self,
226 service_name: str,
227 method_name: str,
228 config: "api_config.ApiConfig",
229 input_handler: "message_util.MessageHandler",
230 output_handlers: List["message_util.MessageHandler"],
231 config_handler: "message_util.MessageHandler",
232 ) -> int:
233 """Dispatch the request.
234
235 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600236 service_name: The fully qualified service name.
237 method_name: The name of the method being called.
238 config: The call configs.
239 input_handler: The request message handler.
240 output_handlers: The response message handlers.
241 config_handler: The config message handler.
Alex Klein1699fab2022-09-08 08:46:06 -0600242
243 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600244 The return code.
Alex Klein1699fab2022-09-08 08:46:06 -0600245
246 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600247 InvalidInputFileError when the input file cannot be read.
248 InvalidOutputFileError when the output file cannot be written.
249 ServiceModuleNotFoundError when the service module cannot be
250 imported.
251 MethodNotFoundError when the method cannot be retrieved from the
252 module.
Alex Klein1699fab2022-09-08 08:46:06 -0600253 """
Alex Klein12fc7ca2023-04-26 18:23:18 -0600254 # Fetch the method options for chroot and method name overrides.
255 method_options = self._get_method_options(service_name, method_name)
256
257 # Check the chroot settings before running.
258 service_options = self._get_service_options(service_name, method_name)
259
260 if self._needs_branch_reexecution(
261 service_options, method_options, config
262 ):
263 logging.info("Re-executing the endpoint on the branched BAPI.")
264 return self._reexecute_branched(
265 input_handler,
266 output_handlers,
267 config_handler,
268 service_name,
269 method_name,
270 )
271
Alex Klein1699fab2022-09-08 08:46:06 -0600272 input_msg = self.get_input_message_instance(service_name, method_name)
273 input_handler.read_into(input_msg)
274
275 # Get an empty output message instance.
276 output_msg = self._get_output_message_instance(
277 service_name, method_name
278 )
279
Alex Klein12fc7ca2023-04-26 18:23:18 -0600280 chroot_reexec = self._needs_chroot_reexecution(
281 service_options, method_options, config
282 )
Alex Klein6cce6f62021-03-02 14:24:05 -0700283
Alex Klein12fc7ca2023-04-26 18:23:18 -0600284 if chroot_reexec and not constants.IS_BRANCHED_CHROMITE:
285 # Can't run inside the SDK with ToT chromite.
286 raise TotSdkError(
287 f"Cannot run ToT {service_name}/{method_name} inside the SDK."
288 )
289 elif chroot_reexec:
Alex Klein1699fab2022-09-08 08:46:06 -0600290 logging.info("Re-executing the endpoint inside the chroot.")
Alex Klein12fc7ca2023-04-26 18:23:18 -0600291 return self._reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600292 input_msg,
293 output_msg,
294 config,
295 input_handler,
296 output_handlers,
297 config_handler,
298 service_name,
299 method_name,
300 )
Alex Klein146d4772019-06-20 13:48:25 -0600301
Alex Klein1699fab2022-09-08 08:46:06 -0600302 # Allow proto-based method name override.
303 if method_options.HasField("implementation_name"):
304 implementation_name = method_options.implementation_name
305 else:
306 implementation_name = method_name
Alex Klein146d4772019-06-20 13:48:25 -0600307
Alex Klein1699fab2022-09-08 08:46:06 -0600308 # Import the module and get the method.
309 module_name = self._get_module_name(service_name, method_name)
310 method_impl = self._GetMethod(module_name, implementation_name)
Alex Klein146d4772019-06-20 13:48:25 -0600311
Alex Klein1699fab2022-09-08 08:46:06 -0600312 # Successfully located; call and return.
313 return_code = method_impl(input_msg, output_msg, config)
314 if return_code is None:
315 return_code = controller.RETURN_CODE_SUCCESS
Alex Klein146d4772019-06-20 13:48:25 -0600316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 for h in output_handlers:
318 h.write_from(output_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600319
Alex Klein1699fab2022-09-08 08:46:06 -0600320 return return_code
Alex Klein146d4772019-06-20 13:48:25 -0600321
Alex Klein12fc7ca2023-04-26 18:23:18 -0600322 def _needs_branch_reexecution(
323 self,
324 service_options: "google.protobuf.Message",
325 method_options: "google.protobuf.Message",
326 config: "api_config.ApiConfig",
327 ) -> bool:
328 """Check if the call needs to be re-executed on the branched BAPI."""
329 if not config.run_endpoint:
330 # Do not re-exec for validate only and mock calls.
331 return False
332
333 if method_options.HasField("method_branched_execution"):
334 # Prefer the method option when set.
335 branched_exec = method_options.method_branched_execution
336 elif service_options.HasField("service_branched_execution"):
337 # Fall back to the service option.
338 branched_exec = service_options.service_branched_execution
339 else:
340 branched_exec = build_api_pb2.EXECUTE_BRANCHED
341
342 if branched_exec in (
343 build_api_pb2.EXECUTE_NOT_SPECIFIED,
344 build_api_pb2.EXECUTE_BRANCHED,
345 ):
346 return not constants.IS_BRANCHED_CHROMITE
347
348 return False
349
350 def _needs_chroot_reexecution(
Alex Klein1699fab2022-09-08 08:46:06 -0600351 self,
352 service_options: "google.protobuf.Message",
353 method_options: "google.protobuf.Message",
354 config: "api_config.ApiConfig",
355 ) -> bool:
Alex Kleina0442682022-10-10 13:47:38 -0600356 """Check the chroot options; execute assertion or note reexec as needed.
Alex Klein146d4772019-06-20 13:48:25 -0600357
Alex Klein1699fab2022-09-08 08:46:06 -0600358 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600359 service_options: The service options.
360 method_options: The method options.
361 config: The Build API call config instance.
Alex Klein146d4772019-06-20 13:48:25 -0600362
Alex Klein1699fab2022-09-08 08:46:06 -0600363 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600364 True iff it needs to be reexeced inside the chroot.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600365
Alex Klein1699fab2022-09-08 08:46:06 -0600366 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600367 cros_build_lib.DieSystemExit when the chroot setting cannot be
368 satisfied.
Alex Klein1699fab2022-09-08 08:46:06 -0600369 """
370 if not config.run_endpoint:
371 # Do not enter the chroot for validate only and mock calls.
372 return False
Alex Klein146d4772019-06-20 13:48:25 -0600373
Alex Klein1699fab2022-09-08 08:46:06 -0600374 chroot_assert = build_api_pb2.NO_ASSERTION
375 if method_options.HasField("method_chroot_assert"):
376 # Prefer the method option when set.
377 chroot_assert = method_options.method_chroot_assert
378 elif service_options.HasField("service_chroot_assert"):
379 # Fall back to the service option.
380 chroot_assert = service_options.service_chroot_assert
Alex Klein146d4772019-06-20 13:48:25 -0600381
Alex Klein1699fab2022-09-08 08:46:06 -0600382 if chroot_assert == build_api_pb2.INSIDE:
383 return not cros_build_lib.IsInsideChroot()
384 elif chroot_assert == build_api_pb2.OUTSIDE:
385 # If it must be run outside we have to already be outside.
386 cros_build_lib.AssertOutsideChroot()
Alex Klein146d4772019-06-20 13:48:25 -0600387
Alex Klein1699fab2022-09-08 08:46:06 -0600388 return False
Alex Klein146d4772019-06-20 13:48:25 -0600389
Alex Klein12fc7ca2023-04-26 18:23:18 -0600390 def _reexecute_branched(
391 self,
392 input_handler: "message_util.MessageHandler",
393 output_handlers: List["message_util.MessageHandler"],
394 config_handler: "message_util.MessageHandler",
395 service_name: str,
396 method_name: str,
397 ):
398 """Re-execute the call on the branched BAPI.
399
400 Args:
401 input_handler: Input message handler.
402 output_handlers: Output message handlers.
403 config_handler: Config message handler.
404 service_name: The name of the service to run.
405 method_name: The name of the method to run.
406 """
407 cmd = [
408 constants.BRANCHED_CHROMITE_DIR / "bin" / "build_api",
409 f"{service_name}/{method_name}",
410 input_handler.input_arg,
411 input_handler.path,
Alex Klein12fc7ca2023-04-26 18:23:18 -0600412 "--debug",
413 ]
Alex Kleindb79a3c2023-05-11 09:45:20 -0600414
Alex Klein12fc7ca2023-04-26 18:23:18 -0600415 for output_handler in output_handlers:
416 cmd += [
417 output_handler.output_arg,
418 output_handler.path,
419 ]
420
Alex Kleindb79a3c2023-05-11 09:45:20 -0600421 # Config is optional, check it was actually used before adding.
422 if config_handler.path:
423 cmd += [
424 config_handler.config_arg,
425 config_handler.path,
426 ]
427
Alex Klein12fc7ca2023-04-26 18:23:18 -0600428 try:
429 result = cros_build_lib.run(
430 cmd,
431 check=False,
432 )
433 except cros_build_lib.RunCommandError:
434 # A non-zero return code will not result in an error, but one
435 # is still thrown when the command cannot be run in the first
436 # place. This is known to happen at least when the PATH does
437 # not include the chromite bin dir.
438 raise CrosSdkNotRunError("Unable to execute the branched BAPI.")
439
440 logging.info(
441 "Endpoint execution completed, return code: %d",
442 result.returncode,
443 )
444
445 return result.returncode
446
447 def _reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600448 self,
449 input_msg: "google.protobuf.Message",
450 output_msg: "google.protobuf.Message",
451 config: "api_config.ApiConfig",
452 input_handler: "message_util.MessageHandler",
453 output_handlers: List["message_util.MessageHandler"],
454 config_handler: "message_util.MessageHandler",
455 service_name: str,
456 method_name: str,
457 ):
458 """Re-execute the service inside the chroot.
Alex Klein146d4772019-06-20 13:48:25 -0600459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600461 input_msg: The parsed input message.
462 output_msg: The empty output message instance.
463 config: The call configs.
464 input_handler: Input message handler.
465 output_handlers: Output message handlers.
466 config_handler: Config message handler.
467 service_name: The name of the service to run.
468 method_name: The name of the method to run.
Alex Klein1699fab2022-09-08 08:46:06 -0600469 """
470 # Parse the chroot and clear the chroot field in the input message.
471 chroot = field_handler.handle_chroot(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600472
Alex Klein1699fab2022-09-08 08:46:06 -0600473 if not chroot.exists():
474 raise InvalidSdkError("Chroot does not exist.")
Alex Klein146d4772019-06-20 13:48:25 -0600475
Brian Norris2d53f9f2023-05-30 12:57:48 -0700476 # This may be redundant with other SDK setup flows, but we need this
477 # tmp directory to exist (including across chromite updates, where the
478 # path may move) before we can write our proto messages (e.g., for
479 # SdkService/Update) to it below.
480 osutils.SafeMakedirsNonRoot(chroot.tmp, mode=0o777)
481
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500482 # Use a ExitStack to avoid the deep nesting this many context managers
483 # introduces.
484 with contextlib.ExitStack() as stack:
Alex Klein1699fab2022-09-08 08:46:06 -0600485 # TempDirs setup.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500486 tempdir = stack.enter_context(chroot.tempdir())
487 sync_tempdir = stack.enter_context(chroot.tempdir())
Alex Klein1699fab2022-09-08 08:46:06 -0600488 # The copy-paths-in context manager to handle Path messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500489 stack.enter_context(
490 field_handler.copy_paths_in(
491 input_msg,
492 chroot.tmp,
Brian Norris5c637c42023-05-04 18:07:48 -0700493 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500494 )
Alex Klein1699fab2022-09-08 08:46:06 -0600495 )
496 # The sync-directories context manager to handle SyncedDir messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500497 stack.enter_context(
498 field_handler.sync_dirs(
499 input_msg,
500 sync_tempdir,
Brian Norris5c637c42023-05-04 18:07:48 -0700501 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500502 )
Alex Klein1699fab2022-09-08 08:46:06 -0600503 )
Alex Klein146d4772019-06-20 13:48:25 -0600504
Alex Klein1699fab2022-09-08 08:46:06 -0600505 # Parse goma.
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800506 chroot.goma = field_handler.handle_goma(
507 input_msg, chroot.path, chroot.out_path
508 )
Alex Kleind1e9e5c2020-12-14 12:32:32 -0700509
Alex Klein1699fab2022-09-08 08:46:06 -0600510 # Parse remoteexec.
511 chroot.remoteexec = field_handler.handle_remoteexec(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600512
Alex Klein54c891a2023-01-24 10:45:41 -0700513 # Build inside-chroot paths for the input, output, and config
514 # messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600515 new_input = os.path.join(tempdir, self.REEXEC_INPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800516 chroot_input = chroot.chroot_path(new_input)
Alex Klein1699fab2022-09-08 08:46:06 -0600517 new_output = os.path.join(tempdir, self.REEXEC_OUTPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800518 chroot_output = chroot.chroot_path(new_output)
Alex Klein1699fab2022-09-08 08:46:06 -0600519 new_config = os.path.join(tempdir, self.REEXEC_CONFIG_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800520 chroot_config = chroot.chroot_path(new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600521
Alex Klein1699fab2022-09-08 08:46:06 -0600522 # Setup the inside-chroot message files.
523 logging.info("Writing input message to: %s", new_input)
524 input_handler.write_from(input_msg, path=new_input)
525 osutils.Touch(new_output)
526 logging.info("Writing config message to: %s", new_config)
527 config_handler.write_from(config.get_proto(), path=new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600528
Alex Klein1699fab2022-09-08 08:46:06 -0600529 # We can use a single output to write the rest of them. Use the
530 # first one as the reexec output and just translate its output in
531 # the rest of the handlers after.
532 output_handler = output_handlers[0]
Alex Klein146d4772019-06-20 13:48:25 -0600533
Alex Klein1699fab2022-09-08 08:46:06 -0600534 cmd = [
535 "build_api",
536 "%s/%s" % (service_name, method_name),
537 input_handler.input_arg,
538 chroot_input,
539 output_handler.output_arg,
540 chroot_output,
541 config_handler.config_arg,
542 chroot_config,
543 "--debug",
544 ]
Alex Klein915cce92019-12-17 14:19:50 -0700545
Alex Klein1699fab2022-09-08 08:46:06 -0600546 try:
Brian Norris3a585d22023-07-19 16:26:11 -0700547 result = chroot.run(
Alex Klein1699fab2022-09-08 08:46:06 -0600548 cmd,
Alex Klein9057b1a2023-05-23 15:44:18 -0600549 cwd=constants.SOURCE_ROOT,
Alex Klein1699fab2022-09-08 08:46:06 -0600550 check=False,
Alex Klein1699fab2022-09-08 08:46:06 -0600551 )
552 except cros_build_lib.RunCommandError:
553 # A non-zero return code will not result in an error, but one
554 # is still thrown when the command cannot be run in the first
555 # place. This is known to happen at least when the PATH does
556 # not include the chromite bin dir.
557 raise CrosSdkNotRunError("Unable to enter the chroot.")
Alex Kleind3394c22020-06-16 14:05:06 -0600558
Alex Klein1699fab2022-09-08 08:46:06 -0600559 logging.info(
560 "Endpoint execution completed, return code: %d",
561 result.returncode,
562 )
Alex Klein146d4772019-06-20 13:48:25 -0600563
Alex Klein1699fab2022-09-08 08:46:06 -0600564 # Transfer result files out of the chroot.
565 output_handler.read_into(output_msg, path=new_output)
566 field_handler.extract_results(input_msg, output_msg, chroot)
Alex Klein9b7331e2019-12-30 14:37:21 -0700567
Alex Klein54c891a2023-01-24 10:45:41 -0700568 # Write out all the response formats.
Alex Klein1699fab2022-09-08 08:46:06 -0600569 for handler in output_handlers:
570 handler.write_from(output_msg)
Joanna Wang92cad812021-11-03 14:52:08 -0700571
Alex Klein1699fab2022-09-08 08:46:06 -0600572 return result.returncode
Alex Klein9b7331e2019-12-30 14:37:21 -0700573
Alex Klein1699fab2022-09-08 08:46:06 -0600574 def _GetMethod(self, module_name: str, method_name: str) -> Callable:
575 """Get the implementation of the method for the service module.
Alex Klein146d4772019-06-20 13:48:25 -0600576
Alex Klein1699fab2022-09-08 08:46:06 -0600577 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600578 module_name: The name of the service module.
579 method_name: The name of the method.
Alex Kleine191ed62020-02-27 15:59:55 -0700580
Alex Klein1699fab2022-09-08 08:46:06 -0600581 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600582 The method.
Alex Klein146d4772019-06-20 13:48:25 -0600583
Alex Klein1699fab2022-09-08 08:46:06 -0600584 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600585 MethodNotFoundError when the method cannot be found in the module.
586 ServiceModuleNotFoundError when the service module cannot be
587 imported.
Alex Klein1699fab2022-09-08 08:46:06 -0600588 """
589 try:
590 module = importlib.import_module(
591 controller.IMPORT_PATTERN % module_name
592 )
593 except ImportError as e:
594 raise ServiceControllerNotFoundError(str(e))
595 try:
596 return getattr(module, method_name)
597 except AttributeError as e:
598 raise MethodNotFoundError(str(e))
Alex Klein146d4772019-06-20 13:48:25 -0600599
600
Tomasz Tylendab4292302021-08-08 18:59:36 +0900601def RegisterServices(router: Router):
Alex Klein1699fab2022-09-08 08:46:06 -0600602 """Register all the services.
Alex Klein146d4772019-06-20 13:48:25 -0600603
Alex Klein1699fab2022-09-08 08:46:06 -0600604 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600605 router: The router.
Alex Klein1699fab2022-09-08 08:46:06 -0600606 """
607 router.Register(android_pb2)
608 router.Register(api_pb2)
609 router.Register(artifacts_pb2)
610 router.Register(binhost_pb2)
Tristan Honscheid52ba4d22023-02-09 11:59:29 -0700611 router.Register(copybot_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600612 router.Register(depgraph_pb2)
Benjamin Shai80317fc2023-08-22 19:33:41 +0000613 router.Register(dlc_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600614 router.Register(firmware_pb2)
615 router.Register(image_pb2)
616 router.Register(metadata_pb2)
Lizzy Preslande723c012022-06-10 05:05:37 +0000617 router.Register(observability_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600618 router.Register(packages_pb2)
619 router.Register(payload_pb2)
620 router.Register(portage_explorer_pb2)
621 router.Register(sdk_pb2)
622 router.Register(sysroot_pb2)
623 router.Register(test_pb2)
624 router.Register(toolchain_pb2)
625 logging.debug("Services registered successfully.")
Alex Klein146d4772019-06-20 13:48:25 -0600626
627
628def GetRouter():
Alex Klein54c891a2023-01-24 10:45:41 -0700629 """Get a router that has had all the services registered."""
Alex Klein1699fab2022-09-08 08:46:06 -0600630 router = Router()
631 RegisterServices(router)
Alex Klein146d4772019-06-20 13:48:25 -0600632
Alex Klein1699fab2022-09-08 08:46:06 -0600633 return router