blob: 7e53f8d1396fb9907158e8c31d67a24e01556476 [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
Tatsuhisa Yamaguchid30a9692023-08-17 19:48:43 +090028from chromite.api.gen.chromite.api import chrome_lkgm_pb2
Tristan Honscheid52ba4d22023-02-09 11:59:29 -070029from chromite.api.gen.chromite.api import copybot_pb2
Alex Klein146d4772019-06-20 13:48:25 -060030from chromite.api.gen.chromite.api import depgraph_pb2
Benjamin Shai80317fc2023-08-22 19:33:41 +000031from chromite.api.gen.chromite.api import dlc_pb2
Jett Rink17ed0f52020-09-25 17:14:31 -060032from chromite.api.gen.chromite.api import firmware_pb2
Alex Klein146d4772019-06-20 13:48:25 -060033from chromite.api.gen.chromite.api import image_pb2
Alex Kleind4d9caa2021-11-10 15:44:52 -070034from chromite.api.gen.chromite.api import metadata_pb2
Lizzy Preslande723c012022-06-10 05:05:37 +000035from chromite.api.gen.chromite.api import observability_pb2
Alex Kleineb77ffa2019-05-28 14:47:44 -060036from chromite.api.gen.chromite.api import packages_pb2
George Engelbrechtfe63c8c2019-08-31 22:51:29 -060037from chromite.api.gen.chromite.api import payload_pb2
Alexander Liu008389c2022-06-27 18:30:11 +000038from chromite.api.gen.chromite.api import portage_explorer_pb2
Alex Klein146d4772019-06-20 13:48:25 -060039from chromite.api.gen.chromite.api import sdk_pb2
40from chromite.api.gen.chromite.api import sysroot_pb2
41from chromite.api.gen.chromite.api import test_pb2
Tiancong Wangaf050172019-07-10 11:52:03 -070042from chromite.api.gen.chromite.api import toolchain_pb2
Alex Klein12fc7ca2023-04-26 18:23:18 -060043from chromite.lib import constants
Alex Klein146d4772019-06-20 13:48:25 -060044from chromite.lib import cros_build_lib
Alex Klein146d4772019-06-20 13:48:25 -060045from chromite.lib import osutils
Alex Klein92341cd2020-02-27 14:11:04 -070046from chromite.utils import memoize
Alex Klein146d4772019-06-20 13:48:25 -060047
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040048
Tomasz Tylendab4292302021-08-08 18:59:36 +090049if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060050 from chromite.third_party import google
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040051
Alex Klein1699fab2022-09-08 08:46:06 -060052 from chromite.api import api_config
53 from chromite.api import message_util
Mike Frysinger88770ef2021-05-21 11:04:00 -040054
Alex Klein92341cd2020-02-27 14:11:04 -070055MethodData = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -060056 "MethodData", ("service_descriptor", "module_name", "method_descriptor")
57)
Alex Klein146d4772019-06-20 13:48:25 -060058
Mike Frysingeref94e4c2020-02-10 23:59:54 -050059
Alex Klein146d4772019-06-20 13:48:25 -060060class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060061 """Base error class for the module."""
Alex Klein146d4772019-06-20 13:48:25 -060062
63
Alex Kleind3394c22020-06-16 14:05:06 -060064class InvalidSdkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060065 """Raised when the SDK is invalid or does not exist."""
Alex Kleind3394c22020-06-16 14:05:06 -060066
67
Alex Klein146d4772019-06-20 13:48:25 -060068class CrosSdkNotRunError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060069 """Raised when the cros_sdk command could not be run to enter the chroot."""
Alex Klein146d4772019-06-20 13:48:25 -060070
71
72# API Service Errors.
73class UnknownServiceError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060074 """Error raised when the requested service has not been registered."""
Alex Klein146d4772019-06-20 13:48:25 -060075
76
77class ControllerModuleNotDefinedError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060078 """Error class for when no controller has been defined for a service."""
Alex Klein146d4772019-06-20 13:48:25 -060079
80
81class ServiceControllerNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060082 """Error raised when the service's controller cannot be imported."""
Alex Klein146d4772019-06-20 13:48:25 -060083
84
Alex Klein12fc7ca2023-04-26 18:23:18 -060085class TotSdkError(Error):
86 """When attempting to run a ToT endpoint inside the SDK."""
87
88
Alex Klein146d4772019-06-20 13:48:25 -060089# API Method Errors.
90class UnknownMethodError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060091 """The service has been defined in the proto, but the method has not."""
Alex Klein146d4772019-06-20 13:48:25 -060092
93
94class MethodNotFoundError(Error):
Alex Klein54c891a2023-01-24 10:45:41 -070095 """The method's implementation cannot be found in the controller."""
Alex Klein146d4772019-06-20 13:48:25 -060096
97
Alex Klein074f94f2023-06-22 10:32:06 -060098class Router:
Alex Klein1699fab2022-09-08 08:46:06 -060099 """Encapsulates the request dispatching logic."""
Alex Klein146d4772019-06-20 13:48:25 -0600100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 REEXEC_INPUT_FILE = "input_proto"
102 REEXEC_OUTPUT_FILE = "output_proto"
103 REEXEC_CONFIG_FILE = "config_proto"
Alex Kleinbd6edf82019-07-18 10:30:49 -0600104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 def __init__(self):
106 self._services = {}
107 self._aliases = {}
108 # All imported generated messages get added to this symbol db.
109 self._sym_db = symbol_database.Default()
Alex Klein146d4772019-06-20 13:48:25 -0600110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 # Save the service and method extension info for looking up
112 # configured extension data.
113 extensions = build_api_pb2.DESCRIPTOR.extensions_by_name
114 self._svc_options_ext = extensions["service_options"]
115 self._method_options_ext = extensions["method_options"]
Alex Klein92341cd2020-02-27 14:11:04 -0700116
Alex Klein1699fab2022-09-08 08:46:06 -0600117 @memoize.Memoize
118 def _get_method_data(self, service_name, method_name):
119 """Get the descriptors and module name for the given Service/Method."""
120 try:
121 svc, module_name = self._services[service_name]
122 except KeyError:
123 raise UnknownServiceError(
124 "The %s service has not been registered." % service_name
125 )
Alex Klein92341cd2020-02-27 14:11:04 -0700126
Alex Klein1699fab2022-09-08 08:46:06 -0600127 try:
128 method_desc = svc.methods_by_name[method_name]
129 except KeyError:
130 raise UnknownMethodError(
131 "The %s method has not been defined in the %s "
132 "service." % (method_name, service_name)
133 )
Alex Klein92341cd2020-02-27 14:11:04 -0700134
Alex Klein1699fab2022-09-08 08:46:06 -0600135 return MethodData(
136 service_descriptor=svc,
137 module_name=module_name,
138 method_descriptor=method_desc,
139 )
Alex Klein92341cd2020-02-27 14:11:04 -0700140
Alex Klein1699fab2022-09-08 08:46:06 -0600141 def get_input_message_instance(self, service_name, method_name):
142 """Get an empty input message instance for the specified method."""
143 method_data = self._get_method_data(service_name, method_name)
144 return self._sym_db.GetPrototype(
145 method_data.method_descriptor.input_type
146 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700147
Alex Klein1699fab2022-09-08 08:46:06 -0600148 def _get_output_message_instance(self, service_name, method_name):
149 """Get an empty output message instance for the specified method."""
150 method_data = self._get_method_data(service_name, method_name)
151 return self._sym_db.GetPrototype(
152 method_data.method_descriptor.output_type
153 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 def _get_module_name(self, service_name, method_name):
156 """Get the name of the module containing the endpoint implementation."""
157 return self._get_method_data(service_name, method_name).module_name
Alex Klein92341cd2020-02-27 14:11:04 -0700158
Alex Klein1699fab2022-09-08 08:46:06 -0600159 def _get_service_options(self, service_name, method_name):
160 """Get the configured service options for the endpoint."""
161 method_data = self._get_method_data(service_name, method_name)
162 svc_extensions = method_data.service_descriptor.GetOptions().Extensions
163 return svc_extensions[self._svc_options_ext]
Alex Klein92341cd2020-02-27 14:11:04 -0700164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 def _get_method_options(self, service_name, method_name):
166 """Get the configured method options for the endpoint."""
167 method_data = self._get_method_data(service_name, method_name)
168 method_extensions = (
169 method_data.method_descriptor.GetOptions().Extensions
170 )
171 return method_extensions[self._method_options_ext]
Alex Klein146d4772019-06-20 13:48:25 -0600172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 def Register(self, proto_module: ModuleType):
174 """Register the services from a generated proto module.
Alex Klein146d4772019-06-20 13:48:25 -0600175
Alex Klein1699fab2022-09-08 08:46:06 -0600176 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600177 proto_module: The generated proto module to register.
Alex Klein146d4772019-06-20 13:48:25 -0600178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600180 ServiceModuleNotDefinedError when the service cannot be found in the
181 provided module.
Alex Klein1699fab2022-09-08 08:46:06 -0600182 """
183 services = proto_module.DESCRIPTOR.services_by_name
184 for service_name, svc in services.items():
185 module_name = (
186 svc.GetOptions().Extensions[self._svc_options_ext].module
187 )
Alex Klein146d4772019-06-20 13:48:25 -0600188
Alex Klein1699fab2022-09-08 08:46:06 -0600189 if not module_name:
190 raise ControllerModuleNotDefinedError(
Alex Klein54c891a2023-01-24 10:45:41 -0700191 "The module must be defined in the service definition: "
192 f"{proto_module}.{service_name}"
Alex Klein1699fab2022-09-08 08:46:06 -0600193 )
Alex Klein146d4772019-06-20 13:48:25 -0600194
Alex Klein1699fab2022-09-08 08:46:06 -0600195 self._services[svc.full_name] = (svc, module_name)
Alex Klein146d4772019-06-20 13:48:25 -0600196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 def ListMethods(self):
198 """List all methods registered with the router."""
199 services = []
200 for service_name, (svc, _module) in self._services.items():
201 svc_visibility = getattr(
202 svc.GetOptions().Extensions[self._svc_options_ext],
203 "service_visibility",
204 build_api_pb2.LV_VISIBLE,
205 )
206 if svc_visibility == build_api_pb2.LV_HIDDEN:
207 continue
Alex Klein6cce6f62021-03-02 14:24:05 -0700208
Alex Klein1699fab2022-09-08 08:46:06 -0600209 for method_name in svc.methods_by_name.keys():
210 method_options = self._get_method_options(
211 service_name, method_name
212 )
213 method_visibility = getattr(
214 method_options,
215 "method_visibility",
216 build_api_pb2.LV_VISIBLE,
217 )
218 if method_visibility == build_api_pb2.LV_HIDDEN:
219 continue
220
221 services.append("%s/%s" % (service_name, method_name))
222
223 return sorted(services)
224
225 def Route(
226 self,
227 service_name: str,
228 method_name: str,
229 config: "api_config.ApiConfig",
230 input_handler: "message_util.MessageHandler",
231 output_handlers: List["message_util.MessageHandler"],
232 config_handler: "message_util.MessageHandler",
233 ) -> int:
234 """Dispatch the request.
235
236 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600237 service_name: The fully qualified service name.
238 method_name: The name of the method being called.
239 config: The call configs.
240 input_handler: The request message handler.
241 output_handlers: The response message handlers.
242 config_handler: The config message handler.
Alex Klein1699fab2022-09-08 08:46:06 -0600243
244 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600245 The return code.
Alex Klein1699fab2022-09-08 08:46:06 -0600246
247 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600248 InvalidInputFileError when the input file cannot be read.
249 InvalidOutputFileError when the output file cannot be written.
250 ServiceModuleNotFoundError when the service module cannot be
251 imported.
252 MethodNotFoundError when the method cannot be retrieved from the
253 module.
Alex Klein1699fab2022-09-08 08:46:06 -0600254 """
Alex Klein12fc7ca2023-04-26 18:23:18 -0600255 # Fetch the method options for chroot and method name overrides.
256 method_options = self._get_method_options(service_name, method_name)
257
258 # Check the chroot settings before running.
259 service_options = self._get_service_options(service_name, method_name)
260
261 if self._needs_branch_reexecution(
262 service_options, method_options, config
263 ):
264 logging.info("Re-executing the endpoint on the branched BAPI.")
265 return self._reexecute_branched(
266 input_handler,
267 output_handlers,
268 config_handler,
269 service_name,
270 method_name,
271 )
272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 input_msg = self.get_input_message_instance(service_name, method_name)
274 input_handler.read_into(input_msg)
275
276 # Get an empty output message instance.
277 output_msg = self._get_output_message_instance(
278 service_name, method_name
279 )
280
Alex Klein12fc7ca2023-04-26 18:23:18 -0600281 chroot_reexec = self._needs_chroot_reexecution(
282 service_options, method_options, config
283 )
Alex Klein6cce6f62021-03-02 14:24:05 -0700284
Alex Klein12fc7ca2023-04-26 18:23:18 -0600285 if chroot_reexec and not constants.IS_BRANCHED_CHROMITE:
286 # Can't run inside the SDK with ToT chromite.
287 raise TotSdkError(
288 f"Cannot run ToT {service_name}/{method_name} inside the SDK."
289 )
290 elif chroot_reexec:
Alex Klein1699fab2022-09-08 08:46:06 -0600291 logging.info("Re-executing the endpoint inside the chroot.")
Alex Klein12fc7ca2023-04-26 18:23:18 -0600292 return self._reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600293 input_msg,
294 output_msg,
295 config,
296 input_handler,
297 output_handlers,
298 config_handler,
299 service_name,
300 method_name,
301 )
Alex Klein146d4772019-06-20 13:48:25 -0600302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 # Allow proto-based method name override.
304 if method_options.HasField("implementation_name"):
305 implementation_name = method_options.implementation_name
306 else:
307 implementation_name = method_name
Alex Klein146d4772019-06-20 13:48:25 -0600308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 # Import the module and get the method.
310 module_name = self._get_module_name(service_name, method_name)
311 method_impl = self._GetMethod(module_name, implementation_name)
Alex Klein146d4772019-06-20 13:48:25 -0600312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 # Successfully located; call and return.
314 return_code = method_impl(input_msg, output_msg, config)
315 if return_code is None:
316 return_code = controller.RETURN_CODE_SUCCESS
Alex Klein146d4772019-06-20 13:48:25 -0600317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 for h in output_handlers:
319 h.write_from(output_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 return return_code
Alex Klein146d4772019-06-20 13:48:25 -0600322
Alex Klein12fc7ca2023-04-26 18:23:18 -0600323 def _needs_branch_reexecution(
324 self,
325 service_options: "google.protobuf.Message",
326 method_options: "google.protobuf.Message",
327 config: "api_config.ApiConfig",
328 ) -> bool:
329 """Check if the call needs to be re-executed on the branched BAPI."""
330 if not config.run_endpoint:
331 # Do not re-exec for validate only and mock calls.
332 return False
333
334 if method_options.HasField("method_branched_execution"):
335 # Prefer the method option when set.
336 branched_exec = method_options.method_branched_execution
337 elif service_options.HasField("service_branched_execution"):
338 # Fall back to the service option.
339 branched_exec = service_options.service_branched_execution
340 else:
341 branched_exec = build_api_pb2.EXECUTE_BRANCHED
342
343 if branched_exec in (
344 build_api_pb2.EXECUTE_NOT_SPECIFIED,
345 build_api_pb2.EXECUTE_BRANCHED,
346 ):
347 return not constants.IS_BRANCHED_CHROMITE
348
349 return False
350
351 def _needs_chroot_reexecution(
Alex Klein1699fab2022-09-08 08:46:06 -0600352 self,
353 service_options: "google.protobuf.Message",
354 method_options: "google.protobuf.Message",
355 config: "api_config.ApiConfig",
356 ) -> bool:
Alex Kleina0442682022-10-10 13:47:38 -0600357 """Check the chroot options; execute assertion or note reexec as needed.
Alex Klein146d4772019-06-20 13:48:25 -0600358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600360 service_options: The service options.
361 method_options: The method options.
362 config: The Build API call config instance.
Alex Klein146d4772019-06-20 13:48:25 -0600363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600365 True iff it needs to be reexeced inside the chroot.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600366
Alex Klein1699fab2022-09-08 08:46:06 -0600367 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600368 cros_build_lib.DieSystemExit when the chroot setting cannot be
369 satisfied.
Alex Klein1699fab2022-09-08 08:46:06 -0600370 """
371 if not config.run_endpoint:
372 # Do not enter the chroot for validate only and mock calls.
373 return False
Alex Klein146d4772019-06-20 13:48:25 -0600374
Alex Klein1699fab2022-09-08 08:46:06 -0600375 chroot_assert = build_api_pb2.NO_ASSERTION
376 if method_options.HasField("method_chroot_assert"):
377 # Prefer the method option when set.
378 chroot_assert = method_options.method_chroot_assert
379 elif service_options.HasField("service_chroot_assert"):
380 # Fall back to the service option.
381 chroot_assert = service_options.service_chroot_assert
Alex Klein146d4772019-06-20 13:48:25 -0600382
Alex Klein1699fab2022-09-08 08:46:06 -0600383 if chroot_assert == build_api_pb2.INSIDE:
384 return not cros_build_lib.IsInsideChroot()
385 elif chroot_assert == build_api_pb2.OUTSIDE:
386 # If it must be run outside we have to already be outside.
387 cros_build_lib.AssertOutsideChroot()
Alex Klein146d4772019-06-20 13:48:25 -0600388
Alex Klein1699fab2022-09-08 08:46:06 -0600389 return False
Alex Klein146d4772019-06-20 13:48:25 -0600390
Alex Klein12fc7ca2023-04-26 18:23:18 -0600391 def _reexecute_branched(
392 self,
393 input_handler: "message_util.MessageHandler",
394 output_handlers: List["message_util.MessageHandler"],
395 config_handler: "message_util.MessageHandler",
396 service_name: str,
397 method_name: str,
398 ):
399 """Re-execute the call on the branched BAPI.
400
401 Args:
402 input_handler: Input message handler.
403 output_handlers: Output message handlers.
404 config_handler: Config message handler.
405 service_name: The name of the service to run.
406 method_name: The name of the method to run.
407 """
408 cmd = [
409 constants.BRANCHED_CHROMITE_DIR / "bin" / "build_api",
410 f"{service_name}/{method_name}",
411 input_handler.input_arg,
412 input_handler.path,
Alex Klein12fc7ca2023-04-26 18:23:18 -0600413 "--debug",
414 ]
Alex Kleindb79a3c2023-05-11 09:45:20 -0600415
Alex Klein12fc7ca2023-04-26 18:23:18 -0600416 for output_handler in output_handlers:
417 cmd += [
418 output_handler.output_arg,
419 output_handler.path,
420 ]
421
Alex Kleindb79a3c2023-05-11 09:45:20 -0600422 # Config is optional, check it was actually used before adding.
423 if config_handler.path:
424 cmd += [
425 config_handler.config_arg,
426 config_handler.path,
427 ]
428
Alex Klein12fc7ca2023-04-26 18:23:18 -0600429 try:
430 result = cros_build_lib.run(
431 cmd,
432 check=False,
433 )
434 except cros_build_lib.RunCommandError:
435 # A non-zero return code will not result in an error, but one
436 # is still thrown when the command cannot be run in the first
437 # place. This is known to happen at least when the PATH does
438 # not include the chromite bin dir.
439 raise CrosSdkNotRunError("Unable to execute the branched BAPI.")
440
441 logging.info(
442 "Endpoint execution completed, return code: %d",
443 result.returncode,
444 )
445
446 return result.returncode
447
448 def _reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600449 self,
450 input_msg: "google.protobuf.Message",
451 output_msg: "google.protobuf.Message",
452 config: "api_config.ApiConfig",
453 input_handler: "message_util.MessageHandler",
454 output_handlers: List["message_util.MessageHandler"],
455 config_handler: "message_util.MessageHandler",
456 service_name: str,
457 method_name: str,
458 ):
459 """Re-execute the service inside the chroot.
Alex Klein146d4772019-06-20 13:48:25 -0600460
Alex Klein1699fab2022-09-08 08:46:06 -0600461 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600462 input_msg: The parsed input message.
463 output_msg: The empty output message instance.
464 config: The call configs.
465 input_handler: Input message handler.
466 output_handlers: Output message handlers.
467 config_handler: Config message handler.
468 service_name: The name of the service to run.
469 method_name: The name of the method to run.
Alex Klein1699fab2022-09-08 08:46:06 -0600470 """
471 # Parse the chroot and clear the chroot field in the input message.
472 chroot = field_handler.handle_chroot(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600473
Alex Klein1699fab2022-09-08 08:46:06 -0600474 if not chroot.exists():
475 raise InvalidSdkError("Chroot does not exist.")
Alex Klein146d4772019-06-20 13:48:25 -0600476
Brian Norris2d53f9f2023-05-30 12:57:48 -0700477 # This may be redundant with other SDK setup flows, but we need this
478 # tmp directory to exist (including across chromite updates, where the
479 # path may move) before we can write our proto messages (e.g., for
480 # SdkService/Update) to it below.
481 osutils.SafeMakedirsNonRoot(chroot.tmp, mode=0o777)
482
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500483 # Use a ExitStack to avoid the deep nesting this many context managers
484 # introduces.
485 with contextlib.ExitStack() as stack:
Alex Klein1699fab2022-09-08 08:46:06 -0600486 # TempDirs setup.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500487 tempdir = stack.enter_context(chroot.tempdir())
488 sync_tempdir = stack.enter_context(chroot.tempdir())
Alex Klein1699fab2022-09-08 08:46:06 -0600489 # The copy-paths-in context manager to handle Path messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500490 stack.enter_context(
491 field_handler.copy_paths_in(
492 input_msg,
493 chroot.tmp,
Brian Norris5c637c42023-05-04 18:07:48 -0700494 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500495 )
Alex Klein1699fab2022-09-08 08:46:06 -0600496 )
497 # The sync-directories context manager to handle SyncedDir messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500498 stack.enter_context(
499 field_handler.sync_dirs(
500 input_msg,
501 sync_tempdir,
Brian Norris5c637c42023-05-04 18:07:48 -0700502 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500503 )
Alex Klein1699fab2022-09-08 08:46:06 -0600504 )
Alex Klein146d4772019-06-20 13:48:25 -0600505
Alex Klein1699fab2022-09-08 08:46:06 -0600506 # Parse goma.
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800507 chroot.goma = field_handler.handle_goma(
508 input_msg, chroot.path, chroot.out_path
509 )
Alex Kleind1e9e5c2020-12-14 12:32:32 -0700510
Alex Klein1699fab2022-09-08 08:46:06 -0600511 # Parse remoteexec.
512 chroot.remoteexec = field_handler.handle_remoteexec(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600513
Alex Klein54c891a2023-01-24 10:45:41 -0700514 # Build inside-chroot paths for the input, output, and config
515 # messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600516 new_input = os.path.join(tempdir, self.REEXEC_INPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800517 chroot_input = chroot.chroot_path(new_input)
Alex Klein1699fab2022-09-08 08:46:06 -0600518 new_output = os.path.join(tempdir, self.REEXEC_OUTPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800519 chroot_output = chroot.chroot_path(new_output)
Alex Klein1699fab2022-09-08 08:46:06 -0600520 new_config = os.path.join(tempdir, self.REEXEC_CONFIG_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800521 chroot_config = chroot.chroot_path(new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600522
Alex Klein1699fab2022-09-08 08:46:06 -0600523 # Setup the inside-chroot message files.
524 logging.info("Writing input message to: %s", new_input)
525 input_handler.write_from(input_msg, path=new_input)
526 osutils.Touch(new_output)
527 logging.info("Writing config message to: %s", new_config)
528 config_handler.write_from(config.get_proto(), path=new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600529
Alex Klein1699fab2022-09-08 08:46:06 -0600530 # We can use a single output to write the rest of them. Use the
531 # first one as the reexec output and just translate its output in
532 # the rest of the handlers after.
533 output_handler = output_handlers[0]
Alex Klein146d4772019-06-20 13:48:25 -0600534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 cmd = [
536 "build_api",
537 "%s/%s" % (service_name, method_name),
538 input_handler.input_arg,
539 chroot_input,
540 output_handler.output_arg,
541 chroot_output,
542 config_handler.config_arg,
543 chroot_config,
544 "--debug",
545 ]
Alex Klein915cce92019-12-17 14:19:50 -0700546
Alex Klein1699fab2022-09-08 08:46:06 -0600547 try:
Brian Norris3a585d22023-07-19 16:26:11 -0700548 result = chroot.run(
Alex Klein1699fab2022-09-08 08:46:06 -0600549 cmd,
Alex Klein9057b1a2023-05-23 15:44:18 -0600550 cwd=constants.SOURCE_ROOT,
Alex Klein1699fab2022-09-08 08:46:06 -0600551 check=False,
Alex Klein1699fab2022-09-08 08:46:06 -0600552 )
553 except cros_build_lib.RunCommandError:
554 # A non-zero return code will not result in an error, but one
555 # is still thrown when the command cannot be run in the first
556 # place. This is known to happen at least when the PATH does
557 # not include the chromite bin dir.
558 raise CrosSdkNotRunError("Unable to enter the chroot.")
Alex Kleind3394c22020-06-16 14:05:06 -0600559
Alex Klein1699fab2022-09-08 08:46:06 -0600560 logging.info(
561 "Endpoint execution completed, return code: %d",
562 result.returncode,
563 )
Alex Klein146d4772019-06-20 13:48:25 -0600564
Alex Klein1699fab2022-09-08 08:46:06 -0600565 # Transfer result files out of the chroot.
566 output_handler.read_into(output_msg, path=new_output)
567 field_handler.extract_results(input_msg, output_msg, chroot)
Alex Klein9b7331e2019-12-30 14:37:21 -0700568
Alex Klein54c891a2023-01-24 10:45:41 -0700569 # Write out all the response formats.
Alex Klein1699fab2022-09-08 08:46:06 -0600570 for handler in output_handlers:
571 handler.write_from(output_msg)
Joanna Wang92cad812021-11-03 14:52:08 -0700572
Alex Klein1699fab2022-09-08 08:46:06 -0600573 return result.returncode
Alex Klein9b7331e2019-12-30 14:37:21 -0700574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 def _GetMethod(self, module_name: str, method_name: str) -> Callable:
576 """Get the implementation of the method for the service module.
Alex Klein146d4772019-06-20 13:48:25 -0600577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600579 module_name: The name of the service module.
580 method_name: The name of the method.
Alex Kleine191ed62020-02-27 15:59:55 -0700581
Alex Klein1699fab2022-09-08 08:46:06 -0600582 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600583 The method.
Alex Klein146d4772019-06-20 13:48:25 -0600584
Alex Klein1699fab2022-09-08 08:46:06 -0600585 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600586 MethodNotFoundError when the method cannot be found in the module.
587 ServiceModuleNotFoundError when the service module cannot be
588 imported.
Alex Klein1699fab2022-09-08 08:46:06 -0600589 """
590 try:
591 module = importlib.import_module(
592 controller.IMPORT_PATTERN % module_name
593 )
594 except ImportError as e:
595 raise ServiceControllerNotFoundError(str(e))
596 try:
597 return getattr(module, method_name)
598 except AttributeError as e:
599 raise MethodNotFoundError(str(e))
Alex Klein146d4772019-06-20 13:48:25 -0600600
601
Tomasz Tylendab4292302021-08-08 18:59:36 +0900602def RegisterServices(router: Router):
Alex Klein1699fab2022-09-08 08:46:06 -0600603 """Register all the services.
Alex Klein146d4772019-06-20 13:48:25 -0600604
Alex Klein1699fab2022-09-08 08:46:06 -0600605 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600606 router: The router.
Alex Klein1699fab2022-09-08 08:46:06 -0600607 """
608 router.Register(android_pb2)
609 router.Register(api_pb2)
610 router.Register(artifacts_pb2)
611 router.Register(binhost_pb2)
Tatsuhisa Yamaguchid30a9692023-08-17 19:48:43 +0900612 router.Register(chrome_lkgm_pb2)
Tristan Honscheid52ba4d22023-02-09 11:59:29 -0700613 router.Register(copybot_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600614 router.Register(depgraph_pb2)
Benjamin Shai80317fc2023-08-22 19:33:41 +0000615 router.Register(dlc_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600616 router.Register(firmware_pb2)
617 router.Register(image_pb2)
618 router.Register(metadata_pb2)
Lizzy Preslande723c012022-06-10 05:05:37 +0000619 router.Register(observability_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600620 router.Register(packages_pb2)
621 router.Register(payload_pb2)
622 router.Register(portage_explorer_pb2)
623 router.Register(sdk_pb2)
624 router.Register(sysroot_pb2)
625 router.Register(test_pb2)
626 router.Register(toolchain_pb2)
627 logging.debug("Services registered successfully.")
Alex Klein146d4772019-06-20 13:48:25 -0600628
629
630def GetRouter():
Alex Klein54c891a2023-01-24 10:45:41 -0700631 """Get a router that has had all the services registered."""
Alex Klein1699fab2022-09-08 08:46:06 -0600632 router = Router()
633 RegisterServices(router)
Alex Klein146d4772019-06-20 13:48:25 -0600634
Alex Klein1699fab2022-09-08 08:46:06 -0600635 return router