blob: bd80d8093f409e4425f111e066a7ea7483344bb6 [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
Jett Rink17ed0f52020-09-25 17:14:31 -060030from chromite.api.gen.chromite.api import firmware_pb2
Alex Klein146d4772019-06-20 13:48:25 -060031from chromite.api.gen.chromite.api import image_pb2
Alex Kleind4d9caa2021-11-10 15:44:52 -070032from chromite.api.gen.chromite.api import metadata_pb2
Lizzy Preslande723c012022-06-10 05:05:37 +000033from chromite.api.gen.chromite.api import observability_pb2
Alex Kleineb77ffa2019-05-28 14:47:44 -060034from chromite.api.gen.chromite.api import packages_pb2
George Engelbrechtfe63c8c2019-08-31 22:51:29 -060035from chromite.api.gen.chromite.api import payload_pb2
Alexander Liu008389c2022-06-27 18:30:11 +000036from chromite.api.gen.chromite.api import portage_explorer_pb2
Alex Klein146d4772019-06-20 13:48:25 -060037from chromite.api.gen.chromite.api import sdk_pb2
38from chromite.api.gen.chromite.api import sysroot_pb2
39from chromite.api.gen.chromite.api import test_pb2
Tiancong Wangaf050172019-07-10 11:52:03 -070040from chromite.api.gen.chromite.api import toolchain_pb2
Alex Klein12fc7ca2023-04-26 18:23:18 -060041from chromite.lib import constants
Alex Klein146d4772019-06-20 13:48:25 -060042from chromite.lib import cros_build_lib
Alex Klein146d4772019-06-20 13:48:25 -060043from chromite.lib import osutils
Alex Klein92341cd2020-02-27 14:11:04 -070044from chromite.utils import memoize
Alex Klein146d4772019-06-20 13:48:25 -060045
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040046
Tomasz Tylendab4292302021-08-08 18:59:36 +090047if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060048 from chromite.third_party import google
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040049
Alex Klein1699fab2022-09-08 08:46:06 -060050 from chromite.api import api_config
51 from chromite.api import message_util
Mike Frysinger88770ef2021-05-21 11:04:00 -040052
Alex Klein92341cd2020-02-27 14:11:04 -070053MethodData = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -060054 "MethodData", ("service_descriptor", "module_name", "method_descriptor")
55)
Alex Klein146d4772019-06-20 13:48:25 -060056
Mike Frysingeref94e4c2020-02-10 23:59:54 -050057
Alex Klein146d4772019-06-20 13:48:25 -060058class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060059 """Base error class for the module."""
Alex Klein146d4772019-06-20 13:48:25 -060060
61
Alex Kleind3394c22020-06-16 14:05:06 -060062class InvalidSdkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060063 """Raised when the SDK is invalid or does not exist."""
Alex Kleind3394c22020-06-16 14:05:06 -060064
65
Alex Klein146d4772019-06-20 13:48:25 -060066class CrosSdkNotRunError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060067 """Raised when the cros_sdk command could not be run to enter the chroot."""
Alex Klein146d4772019-06-20 13:48:25 -060068
69
70# API Service Errors.
71class UnknownServiceError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060072 """Error raised when the requested service has not been registered."""
Alex Klein146d4772019-06-20 13:48:25 -060073
74
75class ControllerModuleNotDefinedError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060076 """Error class for when no controller has been defined for a service."""
Alex Klein146d4772019-06-20 13:48:25 -060077
78
79class ServiceControllerNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060080 """Error raised when the service's controller cannot be imported."""
Alex Klein146d4772019-06-20 13:48:25 -060081
82
Alex Klein12fc7ca2023-04-26 18:23:18 -060083class TotSdkError(Error):
84 """When attempting to run a ToT endpoint inside the SDK."""
85
86
Alex Klein146d4772019-06-20 13:48:25 -060087# API Method Errors.
88class UnknownMethodError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060089 """The service has been defined in the proto, but the method has not."""
Alex Klein146d4772019-06-20 13:48:25 -060090
91
92class MethodNotFoundError(Error):
Alex Klein54c891a2023-01-24 10:45:41 -070093 """The method's implementation cannot be found in the controller."""
Alex Klein146d4772019-06-20 13:48:25 -060094
95
96class Router(object):
Alex Klein1699fab2022-09-08 08:46:06 -060097 """Encapsulates the request dispatching logic."""
Alex Klein146d4772019-06-20 13:48:25 -060098
Alex Klein1699fab2022-09-08 08:46:06 -060099 REEXEC_INPUT_FILE = "input_proto"
100 REEXEC_OUTPUT_FILE = "output_proto"
101 REEXEC_CONFIG_FILE = "config_proto"
Alex Kleinbd6edf82019-07-18 10:30:49 -0600102
Alex Klein1699fab2022-09-08 08:46:06 -0600103 def __init__(self):
104 self._services = {}
105 self._aliases = {}
106 # All imported generated messages get added to this symbol db.
107 self._sym_db = symbol_database.Default()
Alex Klein146d4772019-06-20 13:48:25 -0600108
Alex Klein1699fab2022-09-08 08:46:06 -0600109 # Save the service and method extension info for looking up
110 # configured extension data.
111 extensions = build_api_pb2.DESCRIPTOR.extensions_by_name
112 self._svc_options_ext = extensions["service_options"]
113 self._method_options_ext = extensions["method_options"]
Alex Klein92341cd2020-02-27 14:11:04 -0700114
Alex Klein1699fab2022-09-08 08:46:06 -0600115 @memoize.Memoize
116 def _get_method_data(self, service_name, method_name):
117 """Get the descriptors and module name for the given Service/Method."""
118 try:
119 svc, module_name = self._services[service_name]
120 except KeyError:
121 raise UnknownServiceError(
122 "The %s service has not been registered." % service_name
123 )
Alex Klein92341cd2020-02-27 14:11:04 -0700124
Alex Klein1699fab2022-09-08 08:46:06 -0600125 try:
126 method_desc = svc.methods_by_name[method_name]
127 except KeyError:
128 raise UnknownMethodError(
129 "The %s method has not been defined in the %s "
130 "service." % (method_name, service_name)
131 )
Alex Klein92341cd2020-02-27 14:11:04 -0700132
Alex Klein1699fab2022-09-08 08:46:06 -0600133 return MethodData(
134 service_descriptor=svc,
135 module_name=module_name,
136 method_descriptor=method_desc,
137 )
Alex Klein92341cd2020-02-27 14:11:04 -0700138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 def get_input_message_instance(self, service_name, method_name):
140 """Get an empty input message instance for the specified method."""
141 method_data = self._get_method_data(service_name, method_name)
142 return self._sym_db.GetPrototype(
143 method_data.method_descriptor.input_type
144 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700145
Alex Klein1699fab2022-09-08 08:46:06 -0600146 def _get_output_message_instance(self, service_name, method_name):
147 """Get an empty output message instance for the specified method."""
148 method_data = self._get_method_data(service_name, method_name)
149 return self._sym_db.GetPrototype(
150 method_data.method_descriptor.output_type
151 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700152
Alex Klein1699fab2022-09-08 08:46:06 -0600153 def _get_module_name(self, service_name, method_name):
154 """Get the name of the module containing the endpoint implementation."""
155 return self._get_method_data(service_name, method_name).module_name
Alex Klein92341cd2020-02-27 14:11:04 -0700156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 def _get_service_options(self, service_name, method_name):
158 """Get the configured service options for the endpoint."""
159 method_data = self._get_method_data(service_name, method_name)
160 svc_extensions = method_data.service_descriptor.GetOptions().Extensions
161 return svc_extensions[self._svc_options_ext]
Alex Klein92341cd2020-02-27 14:11:04 -0700162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 def _get_method_options(self, service_name, method_name):
164 """Get the configured method options for the endpoint."""
165 method_data = self._get_method_data(service_name, method_name)
166 method_extensions = (
167 method_data.method_descriptor.GetOptions().Extensions
168 )
169 return method_extensions[self._method_options_ext]
Alex Klein146d4772019-06-20 13:48:25 -0600170
Alex Klein1699fab2022-09-08 08:46:06 -0600171 def Register(self, proto_module: ModuleType):
172 """Register the services from a generated proto module.
Alex Klein146d4772019-06-20 13:48:25 -0600173
Alex Klein1699fab2022-09-08 08:46:06 -0600174 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600175 proto_module: The generated proto module to register.
Alex Klein146d4772019-06-20 13:48:25 -0600176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600178 ServiceModuleNotDefinedError when the service cannot be found in the
179 provided module.
Alex Klein1699fab2022-09-08 08:46:06 -0600180 """
181 services = proto_module.DESCRIPTOR.services_by_name
182 for service_name, svc in services.items():
183 module_name = (
184 svc.GetOptions().Extensions[self._svc_options_ext].module
185 )
Alex Klein146d4772019-06-20 13:48:25 -0600186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 if not module_name:
188 raise ControllerModuleNotDefinedError(
Alex Klein54c891a2023-01-24 10:45:41 -0700189 "The module must be defined in the service definition: "
190 f"{proto_module}.{service_name}"
Alex Klein1699fab2022-09-08 08:46:06 -0600191 )
Alex Klein146d4772019-06-20 13:48:25 -0600192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 self._services[svc.full_name] = (svc, module_name)
Alex Klein146d4772019-06-20 13:48:25 -0600194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 def ListMethods(self):
196 """List all methods registered with the router."""
197 services = []
198 for service_name, (svc, _module) in self._services.items():
199 svc_visibility = getattr(
200 svc.GetOptions().Extensions[self._svc_options_ext],
201 "service_visibility",
202 build_api_pb2.LV_VISIBLE,
203 )
204 if svc_visibility == build_api_pb2.LV_HIDDEN:
205 continue
Alex Klein6cce6f62021-03-02 14:24:05 -0700206
Alex Klein1699fab2022-09-08 08:46:06 -0600207 for method_name in svc.methods_by_name.keys():
208 method_options = self._get_method_options(
209 service_name, method_name
210 )
211 method_visibility = getattr(
212 method_options,
213 "method_visibility",
214 build_api_pb2.LV_VISIBLE,
215 )
216 if method_visibility == build_api_pb2.LV_HIDDEN:
217 continue
218
219 services.append("%s/%s" % (service_name, method_name))
220
221 return sorted(services)
222
223 def Route(
224 self,
225 service_name: str,
226 method_name: str,
227 config: "api_config.ApiConfig",
228 input_handler: "message_util.MessageHandler",
229 output_handlers: List["message_util.MessageHandler"],
230 config_handler: "message_util.MessageHandler",
231 ) -> int:
232 """Dispatch the request.
233
234 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600235 service_name: The fully qualified service name.
236 method_name: The name of the method being called.
237 config: The call configs.
238 input_handler: The request message handler.
239 output_handlers: The response message handlers.
240 config_handler: The config message handler.
Alex Klein1699fab2022-09-08 08:46:06 -0600241
242 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600243 The return code.
Alex Klein1699fab2022-09-08 08:46:06 -0600244
245 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600246 InvalidInputFileError when the input file cannot be read.
247 InvalidOutputFileError when the output file cannot be written.
248 ServiceModuleNotFoundError when the service module cannot be
249 imported.
250 MethodNotFoundError when the method cannot be retrieved from the
251 module.
Alex Klein1699fab2022-09-08 08:46:06 -0600252 """
Alex Klein12fc7ca2023-04-26 18:23:18 -0600253 # Fetch the method options for chroot and method name overrides.
254 method_options = self._get_method_options(service_name, method_name)
255
256 # Check the chroot settings before running.
257 service_options = self._get_service_options(service_name, method_name)
258
259 if self._needs_branch_reexecution(
260 service_options, method_options, config
261 ):
262 logging.info("Re-executing the endpoint on the branched BAPI.")
263 return self._reexecute_branched(
264 input_handler,
265 output_handlers,
266 config_handler,
267 service_name,
268 method_name,
269 )
270
Alex Klein1699fab2022-09-08 08:46:06 -0600271 input_msg = self.get_input_message_instance(service_name, method_name)
272 input_handler.read_into(input_msg)
273
274 # Get an empty output message instance.
275 output_msg = self._get_output_message_instance(
276 service_name, method_name
277 )
278
Alex Klein12fc7ca2023-04-26 18:23:18 -0600279 chroot_reexec = self._needs_chroot_reexecution(
280 service_options, method_options, config
281 )
Alex Klein6cce6f62021-03-02 14:24:05 -0700282
Alex Klein12fc7ca2023-04-26 18:23:18 -0600283 if chroot_reexec and not constants.IS_BRANCHED_CHROMITE:
284 # Can't run inside the SDK with ToT chromite.
285 raise TotSdkError(
286 f"Cannot run ToT {service_name}/{method_name} inside the SDK."
287 )
288 elif chroot_reexec:
Alex Klein1699fab2022-09-08 08:46:06 -0600289 logging.info("Re-executing the endpoint inside the chroot.")
Alex Klein12fc7ca2023-04-26 18:23:18 -0600290 return self._reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600291 input_msg,
292 output_msg,
293 config,
294 input_handler,
295 output_handlers,
296 config_handler,
297 service_name,
298 method_name,
299 )
Alex Klein146d4772019-06-20 13:48:25 -0600300
Alex Klein1699fab2022-09-08 08:46:06 -0600301 # Allow proto-based method name override.
302 if method_options.HasField("implementation_name"):
303 implementation_name = method_options.implementation_name
304 else:
305 implementation_name = method_name
Alex Klein146d4772019-06-20 13:48:25 -0600306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 # Import the module and get the method.
308 module_name = self._get_module_name(service_name, method_name)
309 method_impl = self._GetMethod(module_name, implementation_name)
Alex Klein146d4772019-06-20 13:48:25 -0600310
Alex Klein1699fab2022-09-08 08:46:06 -0600311 # Successfully located; call and return.
312 return_code = method_impl(input_msg, output_msg, config)
313 if return_code is None:
314 return_code = controller.RETURN_CODE_SUCCESS
Alex Klein146d4772019-06-20 13:48:25 -0600315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 for h in output_handlers:
317 h.write_from(output_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600318
Alex Klein1699fab2022-09-08 08:46:06 -0600319 return return_code
Alex Klein146d4772019-06-20 13:48:25 -0600320
Alex Klein12fc7ca2023-04-26 18:23:18 -0600321 def _needs_branch_reexecution(
322 self,
323 service_options: "google.protobuf.Message",
324 method_options: "google.protobuf.Message",
325 config: "api_config.ApiConfig",
326 ) -> bool:
327 """Check if the call needs to be re-executed on the branched BAPI."""
328 if not config.run_endpoint:
329 # Do not re-exec for validate only and mock calls.
330 return False
331
332 if method_options.HasField("method_branched_execution"):
333 # Prefer the method option when set.
334 branched_exec = method_options.method_branched_execution
335 elif service_options.HasField("service_branched_execution"):
336 # Fall back to the service option.
337 branched_exec = service_options.service_branched_execution
338 else:
339 branched_exec = build_api_pb2.EXECUTE_BRANCHED
340
341 if branched_exec in (
342 build_api_pb2.EXECUTE_NOT_SPECIFIED,
343 build_api_pb2.EXECUTE_BRANCHED,
344 ):
345 return not constants.IS_BRANCHED_CHROMITE
346
347 return False
348
349 def _needs_chroot_reexecution(
Alex Klein1699fab2022-09-08 08:46:06 -0600350 self,
351 service_options: "google.protobuf.Message",
352 method_options: "google.protobuf.Message",
353 config: "api_config.ApiConfig",
354 ) -> bool:
Alex Kleina0442682022-10-10 13:47:38 -0600355 """Check the chroot options; execute assertion or note reexec as needed.
Alex Klein146d4772019-06-20 13:48:25 -0600356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600358 service_options: The service options.
359 method_options: The method options.
360 config: The Build API call config instance.
Alex Klein146d4772019-06-20 13:48:25 -0600361
Alex Klein1699fab2022-09-08 08:46:06 -0600362 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600363 True iff it needs to be reexeced inside the chroot.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600366 cros_build_lib.DieSystemExit when the chroot setting cannot be
367 satisfied.
Alex Klein1699fab2022-09-08 08:46:06 -0600368 """
369 if not config.run_endpoint:
370 # Do not enter the chroot for validate only and mock calls.
371 return False
Alex Klein146d4772019-06-20 13:48:25 -0600372
Alex Klein1699fab2022-09-08 08:46:06 -0600373 chroot_assert = build_api_pb2.NO_ASSERTION
374 if method_options.HasField("method_chroot_assert"):
375 # Prefer the method option when set.
376 chroot_assert = method_options.method_chroot_assert
377 elif service_options.HasField("service_chroot_assert"):
378 # Fall back to the service option.
379 chroot_assert = service_options.service_chroot_assert
Alex Klein146d4772019-06-20 13:48:25 -0600380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 if chroot_assert == build_api_pb2.INSIDE:
382 return not cros_build_lib.IsInsideChroot()
383 elif chroot_assert == build_api_pb2.OUTSIDE:
384 # If it must be run outside we have to already be outside.
385 cros_build_lib.AssertOutsideChroot()
Alex Klein146d4772019-06-20 13:48:25 -0600386
Alex Klein1699fab2022-09-08 08:46:06 -0600387 return False
Alex Klein146d4772019-06-20 13:48:25 -0600388
Alex Klein12fc7ca2023-04-26 18:23:18 -0600389 def _reexecute_branched(
390 self,
391 input_handler: "message_util.MessageHandler",
392 output_handlers: List["message_util.MessageHandler"],
393 config_handler: "message_util.MessageHandler",
394 service_name: str,
395 method_name: str,
396 ):
397 """Re-execute the call on the branched BAPI.
398
399 Args:
400 input_handler: Input message handler.
401 output_handlers: Output message handlers.
402 config_handler: Config message handler.
403 service_name: The name of the service to run.
404 method_name: The name of the method to run.
405 """
406 cmd = [
407 constants.BRANCHED_CHROMITE_DIR / "bin" / "build_api",
408 f"{service_name}/{method_name}",
409 input_handler.input_arg,
410 input_handler.path,
411 config_handler.config_arg,
412 config_handler.path,
413 "--debug",
414 ]
415 for output_handler in output_handlers:
416 cmd += [
417 output_handler.output_arg,
418 output_handler.path,
419 ]
420
421 try:
422 result = cros_build_lib.run(
423 cmd,
424 check=False,
425 )
426 except cros_build_lib.RunCommandError:
427 # A non-zero return code will not result in an error, but one
428 # is still thrown when the command cannot be run in the first
429 # place. This is known to happen at least when the PATH does
430 # not include the chromite bin dir.
431 raise CrosSdkNotRunError("Unable to execute the branched BAPI.")
432
433 logging.info(
434 "Endpoint execution completed, return code: %d",
435 result.returncode,
436 )
437
438 return result.returncode
439
440 def _reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600441 self,
442 input_msg: "google.protobuf.Message",
443 output_msg: "google.protobuf.Message",
444 config: "api_config.ApiConfig",
445 input_handler: "message_util.MessageHandler",
446 output_handlers: List["message_util.MessageHandler"],
447 config_handler: "message_util.MessageHandler",
448 service_name: str,
449 method_name: str,
450 ):
451 """Re-execute the service inside the chroot.
Alex Klein146d4772019-06-20 13:48:25 -0600452
Alex Klein1699fab2022-09-08 08:46:06 -0600453 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600454 input_msg: The parsed input message.
455 output_msg: The empty output message instance.
456 config: The call configs.
457 input_handler: Input message handler.
458 output_handlers: Output message handlers.
459 config_handler: Config message handler.
460 service_name: The name of the service to run.
461 method_name: The name of the method to run.
Alex Klein1699fab2022-09-08 08:46:06 -0600462 """
463 # Parse the chroot and clear the chroot field in the input message.
464 chroot = field_handler.handle_chroot(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600465
Alex Klein1699fab2022-09-08 08:46:06 -0600466 if not chroot.exists():
467 raise InvalidSdkError("Chroot does not exist.")
Alex Klein146d4772019-06-20 13:48:25 -0600468
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500469 # Use a ExitStack to avoid the deep nesting this many context managers
470 # introduces.
471 with contextlib.ExitStack() as stack:
Alex Klein1699fab2022-09-08 08:46:06 -0600472 # TempDirs setup.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500473 tempdir = stack.enter_context(chroot.tempdir())
474 sync_tempdir = stack.enter_context(chroot.tempdir())
Alex Klein1699fab2022-09-08 08:46:06 -0600475 # The copy-paths-in context manager to handle Path messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500476 stack.enter_context(
477 field_handler.copy_paths_in(
478 input_msg,
479 chroot.tmp,
Brian Norris5c637c42023-05-04 18:07:48 -0700480 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500481 )
Alex Klein1699fab2022-09-08 08:46:06 -0600482 )
483 # The sync-directories context manager to handle SyncedDir messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500484 stack.enter_context(
485 field_handler.sync_dirs(
486 input_msg,
487 sync_tempdir,
Brian Norris5c637c42023-05-04 18:07:48 -0700488 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500489 )
Alex Klein1699fab2022-09-08 08:46:06 -0600490 )
Alex Klein146d4772019-06-20 13:48:25 -0600491
Alex Klein1699fab2022-09-08 08:46:06 -0600492 # Parse goma.
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800493 chroot.goma = field_handler.handle_goma(
494 input_msg, chroot.path, chroot.out_path
495 )
Alex Kleind1e9e5c2020-12-14 12:32:32 -0700496
Alex Klein1699fab2022-09-08 08:46:06 -0600497 # Parse remoteexec.
498 chroot.remoteexec = field_handler.handle_remoteexec(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600499
Alex Klein54c891a2023-01-24 10:45:41 -0700500 # Build inside-chroot paths for the input, output, and config
501 # messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600502 new_input = os.path.join(tempdir, self.REEXEC_INPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800503 chroot_input = chroot.chroot_path(new_input)
Alex Klein1699fab2022-09-08 08:46:06 -0600504 new_output = os.path.join(tempdir, self.REEXEC_OUTPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800505 chroot_output = chroot.chroot_path(new_output)
Alex Klein1699fab2022-09-08 08:46:06 -0600506 new_config = os.path.join(tempdir, self.REEXEC_CONFIG_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800507 chroot_config = chroot.chroot_path(new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 # Setup the inside-chroot message files.
510 logging.info("Writing input message to: %s", new_input)
511 input_handler.write_from(input_msg, path=new_input)
512 osutils.Touch(new_output)
513 logging.info("Writing config message to: %s", new_config)
514 config_handler.write_from(config.get_proto(), path=new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600515
Alex Klein1699fab2022-09-08 08:46:06 -0600516 # We can use a single output to write the rest of them. Use the
517 # first one as the reexec output and just translate its output in
518 # the rest of the handlers after.
519 output_handler = output_handlers[0]
Alex Klein146d4772019-06-20 13:48:25 -0600520
Alex Klein1699fab2022-09-08 08:46:06 -0600521 cmd = [
522 "build_api",
523 "%s/%s" % (service_name, method_name),
524 input_handler.input_arg,
525 chroot_input,
526 output_handler.output_arg,
527 chroot_output,
528 config_handler.config_arg,
529 chroot_config,
530 "--debug",
531 ]
Alex Klein915cce92019-12-17 14:19:50 -0700532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 try:
534 result = cros_build_lib.run(
535 cmd,
536 enter_chroot=True,
537 chroot_args=chroot.get_enter_args(),
538 check=False,
539 extra_env=chroot.env,
540 )
541 except cros_build_lib.RunCommandError:
542 # A non-zero return code will not result in an error, but one
543 # is still thrown when the command cannot be run in the first
544 # place. This is known to happen at least when the PATH does
545 # not include the chromite bin dir.
546 raise CrosSdkNotRunError("Unable to enter the chroot.")
Alex Kleind3394c22020-06-16 14:05:06 -0600547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 logging.info(
549 "Endpoint execution completed, return code: %d",
550 result.returncode,
551 )
Alex Klein146d4772019-06-20 13:48:25 -0600552
Alex Klein1699fab2022-09-08 08:46:06 -0600553 # Transfer result files out of the chroot.
554 output_handler.read_into(output_msg, path=new_output)
555 field_handler.extract_results(input_msg, output_msg, chroot)
Alex Klein9b7331e2019-12-30 14:37:21 -0700556
Alex Klein54c891a2023-01-24 10:45:41 -0700557 # Write out all the response formats.
Alex Klein1699fab2022-09-08 08:46:06 -0600558 for handler in output_handlers:
559 handler.write_from(output_msg)
Joanna Wang92cad812021-11-03 14:52:08 -0700560
Alex Klein1699fab2022-09-08 08:46:06 -0600561 return result.returncode
Alex Klein9b7331e2019-12-30 14:37:21 -0700562
Alex Klein1699fab2022-09-08 08:46:06 -0600563 def _GetMethod(self, module_name: str, method_name: str) -> Callable:
564 """Get the implementation of the method for the service module.
Alex Klein146d4772019-06-20 13:48:25 -0600565
Alex Klein1699fab2022-09-08 08:46:06 -0600566 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600567 module_name: The name of the service module.
568 method_name: The name of the method.
Alex Kleine191ed62020-02-27 15:59:55 -0700569
Alex Klein1699fab2022-09-08 08:46:06 -0600570 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600571 The method.
Alex Klein146d4772019-06-20 13:48:25 -0600572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600574 MethodNotFoundError when the method cannot be found in the module.
575 ServiceModuleNotFoundError when the service module cannot be
576 imported.
Alex Klein1699fab2022-09-08 08:46:06 -0600577 """
578 try:
579 module = importlib.import_module(
580 controller.IMPORT_PATTERN % module_name
581 )
582 except ImportError as e:
583 raise ServiceControllerNotFoundError(str(e))
584 try:
585 return getattr(module, method_name)
586 except AttributeError as e:
587 raise MethodNotFoundError(str(e))
Alex Klein146d4772019-06-20 13:48:25 -0600588
589
Tomasz Tylendab4292302021-08-08 18:59:36 +0900590def RegisterServices(router: Router):
Alex Klein1699fab2022-09-08 08:46:06 -0600591 """Register all the services.
Alex Klein146d4772019-06-20 13:48:25 -0600592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600594 router: The router.
Alex Klein1699fab2022-09-08 08:46:06 -0600595 """
596 router.Register(android_pb2)
597 router.Register(api_pb2)
598 router.Register(artifacts_pb2)
599 router.Register(binhost_pb2)
Tristan Honscheid52ba4d22023-02-09 11:59:29 -0700600 router.Register(copybot_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600601 router.Register(depgraph_pb2)
602 router.Register(firmware_pb2)
603 router.Register(image_pb2)
604 router.Register(metadata_pb2)
Lizzy Preslande723c012022-06-10 05:05:37 +0000605 router.Register(observability_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600606 router.Register(packages_pb2)
607 router.Register(payload_pb2)
608 router.Register(portage_explorer_pb2)
609 router.Register(sdk_pb2)
610 router.Register(sysroot_pb2)
611 router.Register(test_pb2)
612 router.Register(toolchain_pb2)
613 logging.debug("Services registered successfully.")
Alex Klein146d4772019-06-20 13:48:25 -0600614
615
616def GetRouter():
Alex Klein54c891a2023-01-24 10:45:41 -0700617 """Get a router that has had all the services registered."""
Alex Klein1699fab2022-09-08 08:46:06 -0600618 router = Router()
619 RegisterServices(router)
Alex Klein146d4772019-06-20 13:48:25 -0600620
Alex Klein1699fab2022-09-08 08:46:06 -0600621 return router