blob: 50fe6fed94d7be7b43daf8b1581dee9332a751c2 [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
Alex Klein074f94f2023-06-22 10:32:06 -060096class Router:
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,
Alex Klein12fc7ca2023-04-26 18:23:18 -0600411 "--debug",
412 ]
Alex Kleindb79a3c2023-05-11 09:45:20 -0600413
Alex Klein12fc7ca2023-04-26 18:23:18 -0600414 for output_handler in output_handlers:
415 cmd += [
416 output_handler.output_arg,
417 output_handler.path,
418 ]
419
Alex Kleindb79a3c2023-05-11 09:45:20 -0600420 # Config is optional, check it was actually used before adding.
421 if config_handler.path:
422 cmd += [
423 config_handler.config_arg,
424 config_handler.path,
425 ]
426
Alex Klein12fc7ca2023-04-26 18:23:18 -0600427 try:
428 result = cros_build_lib.run(
429 cmd,
430 check=False,
431 )
432 except cros_build_lib.RunCommandError:
433 # A non-zero return code will not result in an error, but one
434 # is still thrown when the command cannot be run in the first
435 # place. This is known to happen at least when the PATH does
436 # not include the chromite bin dir.
437 raise CrosSdkNotRunError("Unable to execute the branched BAPI.")
438
439 logging.info(
440 "Endpoint execution completed, return code: %d",
441 result.returncode,
442 )
443
444 return result.returncode
445
446 def _reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600447 self,
448 input_msg: "google.protobuf.Message",
449 output_msg: "google.protobuf.Message",
450 config: "api_config.ApiConfig",
451 input_handler: "message_util.MessageHandler",
452 output_handlers: List["message_util.MessageHandler"],
453 config_handler: "message_util.MessageHandler",
454 service_name: str,
455 method_name: str,
456 ):
457 """Re-execute the service inside the chroot.
Alex Klein146d4772019-06-20 13:48:25 -0600458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600460 input_msg: The parsed input message.
461 output_msg: The empty output message instance.
462 config: The call configs.
463 input_handler: Input message handler.
464 output_handlers: Output message handlers.
465 config_handler: Config message handler.
466 service_name: The name of the service to run.
467 method_name: The name of the method to run.
Alex Klein1699fab2022-09-08 08:46:06 -0600468 """
469 # Parse the chroot and clear the chroot field in the input message.
470 chroot = field_handler.handle_chroot(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600471
Alex Klein1699fab2022-09-08 08:46:06 -0600472 if not chroot.exists():
473 raise InvalidSdkError("Chroot does not exist.")
Alex Klein146d4772019-06-20 13:48:25 -0600474
Brian Norris2d53f9f2023-05-30 12:57:48 -0700475 # This may be redundant with other SDK setup flows, but we need this
476 # tmp directory to exist (including across chromite updates, where the
477 # path may move) before we can write our proto messages (e.g., for
478 # SdkService/Update) to it below.
479 osutils.SafeMakedirsNonRoot(chroot.tmp, mode=0o777)
480
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500481 # Use a ExitStack to avoid the deep nesting this many context managers
482 # introduces.
483 with contextlib.ExitStack() as stack:
Alex Klein1699fab2022-09-08 08:46:06 -0600484 # TempDirs setup.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500485 tempdir = stack.enter_context(chroot.tempdir())
486 sync_tempdir = stack.enter_context(chroot.tempdir())
Alex Klein1699fab2022-09-08 08:46:06 -0600487 # The copy-paths-in context manager to handle Path messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500488 stack.enter_context(
489 field_handler.copy_paths_in(
490 input_msg,
491 chroot.tmp,
Brian Norris5c637c42023-05-04 18:07:48 -0700492 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500493 )
Alex Klein1699fab2022-09-08 08:46:06 -0600494 )
495 # The sync-directories context manager to handle SyncedDir messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500496 stack.enter_context(
497 field_handler.sync_dirs(
498 input_msg,
499 sync_tempdir,
Brian Norris5c637c42023-05-04 18:07:48 -0700500 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500501 )
Alex Klein1699fab2022-09-08 08:46:06 -0600502 )
Alex Klein146d4772019-06-20 13:48:25 -0600503
Alex Klein1699fab2022-09-08 08:46:06 -0600504 # Parse goma.
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800505 chroot.goma = field_handler.handle_goma(
506 input_msg, chroot.path, chroot.out_path
507 )
Alex Kleind1e9e5c2020-12-14 12:32:32 -0700508
Alex Klein1699fab2022-09-08 08:46:06 -0600509 # Parse remoteexec.
510 chroot.remoteexec = field_handler.handle_remoteexec(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600511
Alex Klein54c891a2023-01-24 10:45:41 -0700512 # Build inside-chroot paths for the input, output, and config
513 # messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600514 new_input = os.path.join(tempdir, self.REEXEC_INPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800515 chroot_input = chroot.chroot_path(new_input)
Alex Klein1699fab2022-09-08 08:46:06 -0600516 new_output = os.path.join(tempdir, self.REEXEC_OUTPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800517 chroot_output = chroot.chroot_path(new_output)
Alex Klein1699fab2022-09-08 08:46:06 -0600518 new_config = os.path.join(tempdir, self.REEXEC_CONFIG_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800519 chroot_config = chroot.chroot_path(new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600520
Alex Klein1699fab2022-09-08 08:46:06 -0600521 # Setup the inside-chroot message files.
522 logging.info("Writing input message to: %s", new_input)
523 input_handler.write_from(input_msg, path=new_input)
524 osutils.Touch(new_output)
525 logging.info("Writing config message to: %s", new_config)
526 config_handler.write_from(config.get_proto(), path=new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 # We can use a single output to write the rest of them. Use the
529 # first one as the reexec output and just translate its output in
530 # the rest of the handlers after.
531 output_handler = output_handlers[0]
Alex Klein146d4772019-06-20 13:48:25 -0600532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 cmd = [
534 "build_api",
535 "%s/%s" % (service_name, method_name),
536 input_handler.input_arg,
537 chroot_input,
538 output_handler.output_arg,
539 chroot_output,
540 config_handler.config_arg,
541 chroot_config,
542 "--debug",
543 ]
Alex Klein915cce92019-12-17 14:19:50 -0700544
Alex Klein1699fab2022-09-08 08:46:06 -0600545 try:
Brian Norris3a585d22023-07-19 16:26:11 -0700546 result = chroot.run(
Alex Klein1699fab2022-09-08 08:46:06 -0600547 cmd,
Alex Klein9057b1a2023-05-23 15:44:18 -0600548 cwd=constants.SOURCE_ROOT,
Alex Klein1699fab2022-09-08 08:46:06 -0600549 check=False,
Alex Klein1699fab2022-09-08 08:46:06 -0600550 )
551 except cros_build_lib.RunCommandError:
552 # A non-zero return code will not result in an error, but one
553 # is still thrown when the command cannot be run in the first
554 # place. This is known to happen at least when the PATH does
555 # not include the chromite bin dir.
556 raise CrosSdkNotRunError("Unable to enter the chroot.")
Alex Kleind3394c22020-06-16 14:05:06 -0600557
Alex Klein1699fab2022-09-08 08:46:06 -0600558 logging.info(
559 "Endpoint execution completed, return code: %d",
560 result.returncode,
561 )
Alex Klein146d4772019-06-20 13:48:25 -0600562
Alex Klein1699fab2022-09-08 08:46:06 -0600563 # Transfer result files out of the chroot.
564 output_handler.read_into(output_msg, path=new_output)
565 field_handler.extract_results(input_msg, output_msg, chroot)
Alex Klein9b7331e2019-12-30 14:37:21 -0700566
Alex Klein54c891a2023-01-24 10:45:41 -0700567 # Write out all the response formats.
Alex Klein1699fab2022-09-08 08:46:06 -0600568 for handler in output_handlers:
569 handler.write_from(output_msg)
Joanna Wang92cad812021-11-03 14:52:08 -0700570
Alex Klein1699fab2022-09-08 08:46:06 -0600571 return result.returncode
Alex Klein9b7331e2019-12-30 14:37:21 -0700572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 def _GetMethod(self, module_name: str, method_name: str) -> Callable:
574 """Get the implementation of the method for the service module.
Alex Klein146d4772019-06-20 13:48:25 -0600575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600577 module_name: The name of the service module.
578 method_name: The name of the method.
Alex Kleine191ed62020-02-27 15:59:55 -0700579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600581 The method.
Alex Klein146d4772019-06-20 13:48:25 -0600582
Alex Klein1699fab2022-09-08 08:46:06 -0600583 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600584 MethodNotFoundError when the method cannot be found in the module.
585 ServiceModuleNotFoundError when the service module cannot be
586 imported.
Alex Klein1699fab2022-09-08 08:46:06 -0600587 """
588 try:
589 module = importlib.import_module(
590 controller.IMPORT_PATTERN % module_name
591 )
592 except ImportError as e:
593 raise ServiceControllerNotFoundError(str(e))
594 try:
595 return getattr(module, method_name)
596 except AttributeError as e:
597 raise MethodNotFoundError(str(e))
Alex Klein146d4772019-06-20 13:48:25 -0600598
599
Tomasz Tylendab4292302021-08-08 18:59:36 +0900600def RegisterServices(router: Router):
Alex Klein1699fab2022-09-08 08:46:06 -0600601 """Register all the services.
Alex Klein146d4772019-06-20 13:48:25 -0600602
Alex Klein1699fab2022-09-08 08:46:06 -0600603 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600604 router: The router.
Alex Klein1699fab2022-09-08 08:46:06 -0600605 """
606 router.Register(android_pb2)
607 router.Register(api_pb2)
608 router.Register(artifacts_pb2)
609 router.Register(binhost_pb2)
Tristan Honscheid52ba4d22023-02-09 11:59:29 -0700610 router.Register(copybot_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600611 router.Register(depgraph_pb2)
612 router.Register(firmware_pb2)
613 router.Register(image_pb2)
614 router.Register(metadata_pb2)
Lizzy Preslande723c012022-06-10 05:05:37 +0000615 router.Register(observability_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600616 router.Register(packages_pb2)
617 router.Register(payload_pb2)
618 router.Register(portage_explorer_pb2)
619 router.Register(sdk_pb2)
620 router.Register(sysroot_pb2)
621 router.Register(test_pb2)
622 router.Register(toolchain_pb2)
623 logging.debug("Services registered successfully.")
Alex Klein146d4772019-06-20 13:48:25 -0600624
625
626def GetRouter():
Alex Klein54c891a2023-01-24 10:45:41 -0700627 """Get a router that has had all the services registered."""
Alex Klein1699fab2022-09-08 08:46:06 -0600628 router = Router()
629 RegisterServices(router)
Alex Klein146d4772019-06-20 13:48:25 -0600630
Alex Klein1699fab2022-09-08 08:46:06 -0600631 return router