blob: 8ddfa96e5162979b1a4dfc4944fa7343c0c1c478 [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
Trent Aptedbd3cb412023-08-18 11:37:02 +100040from chromite.api.gen.chromite.api import sdk_subtools_pb2
Alex Klein146d4772019-06-20 13:48:25 -060041from chromite.api.gen.chromite.api import sysroot_pb2
42from chromite.api.gen.chromite.api import test_pb2
Tiancong Wangaf050172019-07-10 11:52:03 -070043from chromite.api.gen.chromite.api import toolchain_pb2
Alex Klein12fc7ca2023-04-26 18:23:18 -060044from chromite.lib import constants
Alex Klein146d4772019-06-20 13:48:25 -060045from chromite.lib import cros_build_lib
Alex Klein146d4772019-06-20 13:48:25 -060046from chromite.lib import osutils
Alex Klein92341cd2020-02-27 14:11:04 -070047from chromite.utils import memoize
Alex Klein146d4772019-06-20 13:48:25 -060048
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040049
Tomasz Tylendab4292302021-08-08 18:59:36 +090050if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060051 from chromite.third_party import google
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040052
Alex Klein1699fab2022-09-08 08:46:06 -060053 from chromite.api import api_config
54 from chromite.api import message_util
Mike Frysinger88770ef2021-05-21 11:04:00 -040055
Alex Klein92341cd2020-02-27 14:11:04 -070056MethodData = collections.namedtuple(
Alex Klein1699fab2022-09-08 08:46:06 -060057 "MethodData", ("service_descriptor", "module_name", "method_descriptor")
58)
Alex Klein146d4772019-06-20 13:48:25 -060059
Mike Frysingeref94e4c2020-02-10 23:59:54 -050060
Alex Klein146d4772019-06-20 13:48:25 -060061class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060062 """Base error class for the module."""
Alex Klein146d4772019-06-20 13:48:25 -060063
64
Alex Kleind3394c22020-06-16 14:05:06 -060065class InvalidSdkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060066 """Raised when the SDK is invalid or does not exist."""
Alex Kleind3394c22020-06-16 14:05:06 -060067
68
Alex Klein146d4772019-06-20 13:48:25 -060069class CrosSdkNotRunError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060070 """Raised when the cros_sdk command could not be run to enter the chroot."""
Alex Klein146d4772019-06-20 13:48:25 -060071
72
73# API Service Errors.
74class UnknownServiceError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060075 """Error raised when the requested service has not been registered."""
Alex Klein146d4772019-06-20 13:48:25 -060076
77
78class ControllerModuleNotDefinedError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060079 """Error class for when no controller has been defined for a service."""
Alex Klein146d4772019-06-20 13:48:25 -060080
81
82class ServiceControllerNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060083 """Error raised when the service's controller cannot be imported."""
Alex Klein146d4772019-06-20 13:48:25 -060084
85
Alex Klein12fc7ca2023-04-26 18:23:18 -060086class TotSdkError(Error):
87 """When attempting to run a ToT endpoint inside the SDK."""
88
89
Alex Klein146d4772019-06-20 13:48:25 -060090# API Method Errors.
91class UnknownMethodError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060092 """The service has been defined in the proto, but the method has not."""
Alex Klein146d4772019-06-20 13:48:25 -060093
94
95class MethodNotFoundError(Error):
Alex Klein54c891a2023-01-24 10:45:41 -070096 """The method's implementation cannot be found in the controller."""
Alex Klein146d4772019-06-20 13:48:25 -060097
98
Alex Klein074f94f2023-06-22 10:32:06 -060099class Router:
Alex Klein1699fab2022-09-08 08:46:06 -0600100 """Encapsulates the request dispatching logic."""
Alex Klein146d4772019-06-20 13:48:25 -0600101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 REEXEC_INPUT_FILE = "input_proto"
103 REEXEC_OUTPUT_FILE = "output_proto"
104 REEXEC_CONFIG_FILE = "config_proto"
Alex Kleinbd6edf82019-07-18 10:30:49 -0600105
Alex Klein1699fab2022-09-08 08:46:06 -0600106 def __init__(self):
107 self._services = {}
108 self._aliases = {}
109 # All imported generated messages get added to this symbol db.
110 self._sym_db = symbol_database.Default()
Alex Klein146d4772019-06-20 13:48:25 -0600111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 # Save the service and method extension info for looking up
113 # configured extension data.
114 extensions = build_api_pb2.DESCRIPTOR.extensions_by_name
115 self._svc_options_ext = extensions["service_options"]
116 self._method_options_ext = extensions["method_options"]
Alex Klein92341cd2020-02-27 14:11:04 -0700117
Alex Klein1699fab2022-09-08 08:46:06 -0600118 @memoize.Memoize
119 def _get_method_data(self, service_name, method_name):
120 """Get the descriptors and module name for the given Service/Method."""
121 try:
122 svc, module_name = self._services[service_name]
123 except KeyError:
124 raise UnknownServiceError(
125 "The %s service has not been registered." % service_name
126 )
Alex Klein92341cd2020-02-27 14:11:04 -0700127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 try:
129 method_desc = svc.methods_by_name[method_name]
130 except KeyError:
131 raise UnknownMethodError(
132 "The %s method has not been defined in the %s "
133 "service." % (method_name, service_name)
134 )
Alex Klein92341cd2020-02-27 14:11:04 -0700135
Alex Klein1699fab2022-09-08 08:46:06 -0600136 return MethodData(
137 service_descriptor=svc,
138 module_name=module_name,
139 method_descriptor=method_desc,
140 )
Alex Klein92341cd2020-02-27 14:11:04 -0700141
Alex Klein1699fab2022-09-08 08:46:06 -0600142 def get_input_message_instance(self, service_name, method_name):
143 """Get an empty input message instance for the specified method."""
144 method_data = self._get_method_data(service_name, method_name)
145 return self._sym_db.GetPrototype(
146 method_data.method_descriptor.input_type
147 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700148
Alex Klein1699fab2022-09-08 08:46:06 -0600149 def _get_output_message_instance(self, service_name, method_name):
150 """Get an empty output message instance for the specified method."""
151 method_data = self._get_method_data(service_name, method_name)
152 return self._sym_db.GetPrototype(
153 method_data.method_descriptor.output_type
154 )()
Alex Klein92341cd2020-02-27 14:11:04 -0700155
Alex Klein1699fab2022-09-08 08:46:06 -0600156 def _get_module_name(self, service_name, method_name):
157 """Get the name of the module containing the endpoint implementation."""
158 return self._get_method_data(service_name, method_name).module_name
Alex Klein92341cd2020-02-27 14:11:04 -0700159
Alex Klein1699fab2022-09-08 08:46:06 -0600160 def _get_service_options(self, service_name, method_name):
161 """Get the configured service options for the endpoint."""
162 method_data = self._get_method_data(service_name, method_name)
163 svc_extensions = method_data.service_descriptor.GetOptions().Extensions
164 return svc_extensions[self._svc_options_ext]
Alex Klein92341cd2020-02-27 14:11:04 -0700165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 def _get_method_options(self, service_name, method_name):
167 """Get the configured method options for the endpoint."""
168 method_data = self._get_method_data(service_name, method_name)
169 method_extensions = (
170 method_data.method_descriptor.GetOptions().Extensions
171 )
172 return method_extensions[self._method_options_ext]
Alex Klein146d4772019-06-20 13:48:25 -0600173
Alex Klein1699fab2022-09-08 08:46:06 -0600174 def Register(self, proto_module: ModuleType):
175 """Register the services from a generated proto module.
Alex Klein146d4772019-06-20 13:48:25 -0600176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600178 proto_module: The generated proto module to register.
Alex Klein146d4772019-06-20 13:48:25 -0600179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600181 ServiceModuleNotDefinedError when the service cannot be found in the
182 provided module.
Alex Klein1699fab2022-09-08 08:46:06 -0600183 """
184 services = proto_module.DESCRIPTOR.services_by_name
185 for service_name, svc in services.items():
186 module_name = (
187 svc.GetOptions().Extensions[self._svc_options_ext].module
188 )
Alex Klein146d4772019-06-20 13:48:25 -0600189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 if not module_name:
191 raise ControllerModuleNotDefinedError(
Alex Klein54c891a2023-01-24 10:45:41 -0700192 "The module must be defined in the service definition: "
193 f"{proto_module}.{service_name}"
Alex Klein1699fab2022-09-08 08:46:06 -0600194 )
Alex Klein146d4772019-06-20 13:48:25 -0600195
Alex Klein1699fab2022-09-08 08:46:06 -0600196 self._services[svc.full_name] = (svc, module_name)
Alex Klein146d4772019-06-20 13:48:25 -0600197
Alex Klein1699fab2022-09-08 08:46:06 -0600198 def ListMethods(self):
199 """List all methods registered with the router."""
200 services = []
201 for service_name, (svc, _module) in self._services.items():
202 svc_visibility = getattr(
203 svc.GetOptions().Extensions[self._svc_options_ext],
204 "service_visibility",
205 build_api_pb2.LV_VISIBLE,
206 )
207 if svc_visibility == build_api_pb2.LV_HIDDEN:
208 continue
Alex Klein6cce6f62021-03-02 14:24:05 -0700209
Alex Klein1699fab2022-09-08 08:46:06 -0600210 for method_name in svc.methods_by_name.keys():
211 method_options = self._get_method_options(
212 service_name, method_name
213 )
214 method_visibility = getattr(
215 method_options,
216 "method_visibility",
217 build_api_pb2.LV_VISIBLE,
218 )
219 if method_visibility == build_api_pb2.LV_HIDDEN:
220 continue
221
222 services.append("%s/%s" % (service_name, method_name))
223
224 return sorted(services)
225
226 def Route(
227 self,
228 service_name: str,
229 method_name: str,
230 config: "api_config.ApiConfig",
231 input_handler: "message_util.MessageHandler",
232 output_handlers: List["message_util.MessageHandler"],
233 config_handler: "message_util.MessageHandler",
234 ) -> int:
235 """Dispatch the request.
236
237 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600238 service_name: The fully qualified service name.
239 method_name: The name of the method being called.
240 config: The call configs.
241 input_handler: The request message handler.
242 output_handlers: The response message handlers.
243 config_handler: The config message handler.
Alex Klein1699fab2022-09-08 08:46:06 -0600244
245 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600246 The return code.
Alex Klein1699fab2022-09-08 08:46:06 -0600247
248 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600249 InvalidInputFileError when the input file cannot be read.
250 InvalidOutputFileError when the output file cannot be written.
251 ServiceModuleNotFoundError when the service module cannot be
252 imported.
253 MethodNotFoundError when the method cannot be retrieved from the
254 module.
Alex Klein1699fab2022-09-08 08:46:06 -0600255 """
Alex Klein12fc7ca2023-04-26 18:23:18 -0600256 # Fetch the method options for chroot and method name overrides.
257 method_options = self._get_method_options(service_name, method_name)
258
259 # Check the chroot settings before running.
260 service_options = self._get_service_options(service_name, method_name)
261
262 if self._needs_branch_reexecution(
263 service_options, method_options, config
264 ):
265 logging.info("Re-executing the endpoint on the branched BAPI.")
266 return self._reexecute_branched(
267 input_handler,
268 output_handlers,
269 config_handler,
270 service_name,
271 method_name,
272 )
273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 input_msg = self.get_input_message_instance(service_name, method_name)
275 input_handler.read_into(input_msg)
276
277 # Get an empty output message instance.
278 output_msg = self._get_output_message_instance(
279 service_name, method_name
280 )
281
Alex Klein12fc7ca2023-04-26 18:23:18 -0600282 chroot_reexec = self._needs_chroot_reexecution(
283 service_options, method_options, config
284 )
Alex Klein6cce6f62021-03-02 14:24:05 -0700285
Alex Klein12fc7ca2023-04-26 18:23:18 -0600286 if chroot_reexec and not constants.IS_BRANCHED_CHROMITE:
287 # Can't run inside the SDK with ToT chromite.
288 raise TotSdkError(
289 f"Cannot run ToT {service_name}/{method_name} inside the SDK."
290 )
291 elif chroot_reexec:
Alex Klein1699fab2022-09-08 08:46:06 -0600292 logging.info("Re-executing the endpoint inside the chroot.")
Alex Klein12fc7ca2023-04-26 18:23:18 -0600293 return self._reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600294 input_msg,
295 output_msg,
296 config,
297 input_handler,
298 output_handlers,
299 config_handler,
300 service_name,
301 method_name,
302 )
Alex Klein146d4772019-06-20 13:48:25 -0600303
Alex Klein1699fab2022-09-08 08:46:06 -0600304 # Allow proto-based method name override.
305 if method_options.HasField("implementation_name"):
306 implementation_name = method_options.implementation_name
307 else:
308 implementation_name = method_name
Alex Klein146d4772019-06-20 13:48:25 -0600309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 # Import the module and get the method.
311 module_name = self._get_module_name(service_name, method_name)
312 method_impl = self._GetMethod(module_name, implementation_name)
Alex Klein146d4772019-06-20 13:48:25 -0600313
Alex Klein1699fab2022-09-08 08:46:06 -0600314 # Successfully located; call and return.
315 return_code = method_impl(input_msg, output_msg, config)
316 if return_code is None:
317 return_code = controller.RETURN_CODE_SUCCESS
Alex Klein146d4772019-06-20 13:48:25 -0600318
Alex Klein1699fab2022-09-08 08:46:06 -0600319 for h in output_handlers:
320 h.write_from(output_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600321
Alex Klein1699fab2022-09-08 08:46:06 -0600322 return return_code
Alex Klein146d4772019-06-20 13:48:25 -0600323
Alex Klein12fc7ca2023-04-26 18:23:18 -0600324 def _needs_branch_reexecution(
325 self,
326 service_options: "google.protobuf.Message",
327 method_options: "google.protobuf.Message",
328 config: "api_config.ApiConfig",
329 ) -> bool:
330 """Check if the call needs to be re-executed on the branched BAPI."""
331 if not config.run_endpoint:
332 # Do not re-exec for validate only and mock calls.
333 return False
334
335 if method_options.HasField("method_branched_execution"):
336 # Prefer the method option when set.
337 branched_exec = method_options.method_branched_execution
338 elif service_options.HasField("service_branched_execution"):
339 # Fall back to the service option.
340 branched_exec = service_options.service_branched_execution
341 else:
342 branched_exec = build_api_pb2.EXECUTE_BRANCHED
343
344 if branched_exec in (
345 build_api_pb2.EXECUTE_NOT_SPECIFIED,
346 build_api_pb2.EXECUTE_BRANCHED,
347 ):
348 return not constants.IS_BRANCHED_CHROMITE
349
350 return False
351
352 def _needs_chroot_reexecution(
Alex Klein1699fab2022-09-08 08:46:06 -0600353 self,
354 service_options: "google.protobuf.Message",
355 method_options: "google.protobuf.Message",
356 config: "api_config.ApiConfig",
357 ) -> bool:
Alex Kleina0442682022-10-10 13:47:38 -0600358 """Check the chroot options; execute assertion or note reexec as needed.
Alex Klein146d4772019-06-20 13:48:25 -0600359
Alex Klein1699fab2022-09-08 08:46:06 -0600360 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600361 service_options: The service options.
362 method_options: The method options.
363 config: The Build API call config instance.
Alex Klein146d4772019-06-20 13:48:25 -0600364
Alex Klein1699fab2022-09-08 08:46:06 -0600365 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600366 True iff it needs to be reexeced inside the chroot.
Alex Kleinbd6edf82019-07-18 10:30:49 -0600367
Alex Klein1699fab2022-09-08 08:46:06 -0600368 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600369 cros_build_lib.DieSystemExit when the chroot setting cannot be
370 satisfied.
Alex Klein1699fab2022-09-08 08:46:06 -0600371 """
372 if not config.run_endpoint:
373 # Do not enter the chroot for validate only and mock calls.
374 return False
Alex Klein146d4772019-06-20 13:48:25 -0600375
Alex Klein1699fab2022-09-08 08:46:06 -0600376 chroot_assert = build_api_pb2.NO_ASSERTION
377 if method_options.HasField("method_chroot_assert"):
378 # Prefer the method option when set.
379 chroot_assert = method_options.method_chroot_assert
380 elif service_options.HasField("service_chroot_assert"):
381 # Fall back to the service option.
382 chroot_assert = service_options.service_chroot_assert
Alex Klein146d4772019-06-20 13:48:25 -0600383
Alex Klein1699fab2022-09-08 08:46:06 -0600384 if chroot_assert == build_api_pb2.INSIDE:
385 return not cros_build_lib.IsInsideChroot()
386 elif chroot_assert == build_api_pb2.OUTSIDE:
387 # If it must be run outside we have to already be outside.
388 cros_build_lib.AssertOutsideChroot()
Alex Klein146d4772019-06-20 13:48:25 -0600389
Alex Klein1699fab2022-09-08 08:46:06 -0600390 return False
Alex Klein146d4772019-06-20 13:48:25 -0600391
Alex Klein12fc7ca2023-04-26 18:23:18 -0600392 def _reexecute_branched(
393 self,
394 input_handler: "message_util.MessageHandler",
395 output_handlers: List["message_util.MessageHandler"],
396 config_handler: "message_util.MessageHandler",
397 service_name: str,
398 method_name: str,
399 ):
400 """Re-execute the call on the branched BAPI.
401
402 Args:
403 input_handler: Input message handler.
404 output_handlers: Output message handlers.
405 config_handler: Config message handler.
406 service_name: The name of the service to run.
407 method_name: The name of the method to run.
408 """
409 cmd = [
410 constants.BRANCHED_CHROMITE_DIR / "bin" / "build_api",
411 f"{service_name}/{method_name}",
412 input_handler.input_arg,
413 input_handler.path,
Alex Klein12fc7ca2023-04-26 18:23:18 -0600414 "--debug",
415 ]
Alex Kleindb79a3c2023-05-11 09:45:20 -0600416
Alex Klein12fc7ca2023-04-26 18:23:18 -0600417 for output_handler in output_handlers:
418 cmd += [
419 output_handler.output_arg,
420 output_handler.path,
421 ]
422
Alex Kleindb79a3c2023-05-11 09:45:20 -0600423 # Config is optional, check it was actually used before adding.
424 if config_handler.path:
425 cmd += [
426 config_handler.config_arg,
427 config_handler.path,
428 ]
429
Alex Klein12fc7ca2023-04-26 18:23:18 -0600430 try:
431 result = cros_build_lib.run(
432 cmd,
433 check=False,
434 )
435 except cros_build_lib.RunCommandError:
436 # A non-zero return code will not result in an error, but one
437 # is still thrown when the command cannot be run in the first
438 # place. This is known to happen at least when the PATH does
439 # not include the chromite bin dir.
440 raise CrosSdkNotRunError("Unable to execute the branched BAPI.")
441
442 logging.info(
443 "Endpoint execution completed, return code: %d",
444 result.returncode,
445 )
446
447 return result.returncode
448
449 def _reexecute_inside(
Alex Klein1699fab2022-09-08 08:46:06 -0600450 self,
451 input_msg: "google.protobuf.Message",
452 output_msg: "google.protobuf.Message",
453 config: "api_config.ApiConfig",
454 input_handler: "message_util.MessageHandler",
455 output_handlers: List["message_util.MessageHandler"],
456 config_handler: "message_util.MessageHandler",
457 service_name: str,
458 method_name: str,
459 ):
460 """Re-execute the service inside the chroot.
Alex Klein146d4772019-06-20 13:48:25 -0600461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600463 input_msg: The parsed input message.
464 output_msg: The empty output message instance.
465 config: The call configs.
466 input_handler: Input message handler.
467 output_handlers: Output message handlers.
468 config_handler: Config message handler.
469 service_name: The name of the service to run.
470 method_name: The name of the method to run.
Alex Klein1699fab2022-09-08 08:46:06 -0600471 """
472 # Parse the chroot and clear the chroot field in the input message.
473 chroot = field_handler.handle_chroot(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600474
Alex Klein1699fab2022-09-08 08:46:06 -0600475 if not chroot.exists():
476 raise InvalidSdkError("Chroot does not exist.")
Alex Klein146d4772019-06-20 13:48:25 -0600477
Brian Norris2d53f9f2023-05-30 12:57:48 -0700478 # This may be redundant with other SDK setup flows, but we need this
479 # tmp directory to exist (including across chromite updates, where the
480 # path may move) before we can write our proto messages (e.g., for
481 # SdkService/Update) to it below.
482 osutils.SafeMakedirsNonRoot(chroot.tmp, mode=0o777)
483
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500484 # Use a ExitStack to avoid the deep nesting this many context managers
485 # introduces.
486 with contextlib.ExitStack() as stack:
Alex Klein1699fab2022-09-08 08:46:06 -0600487 # TempDirs setup.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500488 tempdir = stack.enter_context(chroot.tempdir())
489 sync_tempdir = stack.enter_context(chroot.tempdir())
Alex Klein1699fab2022-09-08 08:46:06 -0600490 # The copy-paths-in context manager to handle Path messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500491 stack.enter_context(
492 field_handler.copy_paths_in(
493 input_msg,
494 chroot.tmp,
Brian Norris5c637c42023-05-04 18:07:48 -0700495 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500496 )
Alex Klein1699fab2022-09-08 08:46:06 -0600497 )
498 # The sync-directories context manager to handle SyncedDir messages.
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500499 stack.enter_context(
500 field_handler.sync_dirs(
501 input_msg,
502 sync_tempdir,
Brian Norris5c637c42023-05-04 18:07:48 -0700503 chroot=chroot,
Mike Frysingerc2e72b42023-03-03 22:34:04 -0500504 )
Alex Klein1699fab2022-09-08 08:46:06 -0600505 )
Alex Klein146d4772019-06-20 13:48:25 -0600506
Alex Klein1699fab2022-09-08 08:46:06 -0600507 # Parse goma.
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800508 chroot.goma = field_handler.handle_goma(
509 input_msg, chroot.path, chroot.out_path
510 )
Alex Kleind1e9e5c2020-12-14 12:32:32 -0700511
Alex Klein1699fab2022-09-08 08:46:06 -0600512 # Parse remoteexec.
513 chroot.remoteexec = field_handler.handle_remoteexec(input_msg)
Alex Klein146d4772019-06-20 13:48:25 -0600514
Alex Klein54c891a2023-01-24 10:45:41 -0700515 # Build inside-chroot paths for the input, output, and config
516 # messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600517 new_input = os.path.join(tempdir, self.REEXEC_INPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800518 chroot_input = chroot.chroot_path(new_input)
Alex Klein1699fab2022-09-08 08:46:06 -0600519 new_output = os.path.join(tempdir, self.REEXEC_OUTPUT_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800520 chroot_output = chroot.chroot_path(new_output)
Alex Klein1699fab2022-09-08 08:46:06 -0600521 new_config = os.path.join(tempdir, self.REEXEC_CONFIG_FILE)
Brian Norris8faf47b2023-03-03 16:19:08 -0800522 chroot_config = chroot.chroot_path(new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600523
Alex Klein1699fab2022-09-08 08:46:06 -0600524 # Setup the inside-chroot message files.
525 logging.info("Writing input message to: %s", new_input)
526 input_handler.write_from(input_msg, path=new_input)
527 osutils.Touch(new_output)
528 logging.info("Writing config message to: %s", new_config)
529 config_handler.write_from(config.get_proto(), path=new_config)
Alex Klein146d4772019-06-20 13:48:25 -0600530
Alex Klein1699fab2022-09-08 08:46:06 -0600531 # We can use a single output to write the rest of them. Use the
532 # first one as the reexec output and just translate its output in
533 # the rest of the handlers after.
534 output_handler = output_handlers[0]
Alex Klein146d4772019-06-20 13:48:25 -0600535
Alex Klein1699fab2022-09-08 08:46:06 -0600536 cmd = [
537 "build_api",
538 "%s/%s" % (service_name, method_name),
539 input_handler.input_arg,
540 chroot_input,
541 output_handler.output_arg,
542 chroot_output,
543 config_handler.config_arg,
544 chroot_config,
545 "--debug",
546 ]
Alex Klein915cce92019-12-17 14:19:50 -0700547
Alex Klein1699fab2022-09-08 08:46:06 -0600548 try:
Brian Norris3a585d22023-07-19 16:26:11 -0700549 result = chroot.run(
Alex Klein1699fab2022-09-08 08:46:06 -0600550 cmd,
Alex Klein9057b1a2023-05-23 15:44:18 -0600551 cwd=constants.SOURCE_ROOT,
Alex Klein1699fab2022-09-08 08:46:06 -0600552 check=False,
Alex Klein1699fab2022-09-08 08:46:06 -0600553 )
554 except cros_build_lib.RunCommandError:
555 # A non-zero return code will not result in an error, but one
556 # is still thrown when the command cannot be run in the first
557 # place. This is known to happen at least when the PATH does
558 # not include the chromite bin dir.
559 raise CrosSdkNotRunError("Unable to enter the chroot.")
Alex Kleind3394c22020-06-16 14:05:06 -0600560
Alex Klein1699fab2022-09-08 08:46:06 -0600561 logging.info(
562 "Endpoint execution completed, return code: %d",
563 result.returncode,
564 )
Alex Klein146d4772019-06-20 13:48:25 -0600565
Alex Klein1699fab2022-09-08 08:46:06 -0600566 # Transfer result files out of the chroot.
567 output_handler.read_into(output_msg, path=new_output)
568 field_handler.extract_results(input_msg, output_msg, chroot)
Alex Klein9b7331e2019-12-30 14:37:21 -0700569
Alex Klein54c891a2023-01-24 10:45:41 -0700570 # Write out all the response formats.
Alex Klein1699fab2022-09-08 08:46:06 -0600571 for handler in output_handlers:
572 handler.write_from(output_msg)
Joanna Wang92cad812021-11-03 14:52:08 -0700573
Alex Klein1699fab2022-09-08 08:46:06 -0600574 return result.returncode
Alex Klein9b7331e2019-12-30 14:37:21 -0700575
Alex Klein1699fab2022-09-08 08:46:06 -0600576 def _GetMethod(self, module_name: str, method_name: str) -> Callable:
577 """Get the implementation of the method for the service module.
Alex Klein146d4772019-06-20 13:48:25 -0600578
Alex Klein1699fab2022-09-08 08:46:06 -0600579 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600580 module_name: The name of the service module.
581 method_name: The name of the method.
Alex Kleine191ed62020-02-27 15:59:55 -0700582
Alex Klein1699fab2022-09-08 08:46:06 -0600583 Returns:
Alex Kleina0442682022-10-10 13:47:38 -0600584 The method.
Alex Klein146d4772019-06-20 13:48:25 -0600585
Alex Klein1699fab2022-09-08 08:46:06 -0600586 Raises:
Alex Kleina0442682022-10-10 13:47:38 -0600587 MethodNotFoundError when the method cannot be found in the module.
588 ServiceModuleNotFoundError when the service module cannot be
589 imported.
Alex Klein1699fab2022-09-08 08:46:06 -0600590 """
591 try:
592 module = importlib.import_module(
593 controller.IMPORT_PATTERN % module_name
594 )
595 except ImportError as e:
596 raise ServiceControllerNotFoundError(str(e))
597 try:
598 return getattr(module, method_name)
599 except AttributeError as e:
600 raise MethodNotFoundError(str(e))
Alex Klein146d4772019-06-20 13:48:25 -0600601
602
Tomasz Tylendab4292302021-08-08 18:59:36 +0900603def RegisterServices(router: Router):
Alex Klein1699fab2022-09-08 08:46:06 -0600604 """Register all the services.
Alex Klein146d4772019-06-20 13:48:25 -0600605
Alex Klein1699fab2022-09-08 08:46:06 -0600606 Args:
Alex Kleina0442682022-10-10 13:47:38 -0600607 router: The router.
Alex Klein1699fab2022-09-08 08:46:06 -0600608 """
609 router.Register(android_pb2)
610 router.Register(api_pb2)
611 router.Register(artifacts_pb2)
612 router.Register(binhost_pb2)
Tatsuhisa Yamaguchid30a9692023-08-17 19:48:43 +0900613 router.Register(chrome_lkgm_pb2)
Tristan Honscheid52ba4d22023-02-09 11:59:29 -0700614 router.Register(copybot_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600615 router.Register(depgraph_pb2)
Benjamin Shai80317fc2023-08-22 19:33:41 +0000616 router.Register(dlc_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600617 router.Register(firmware_pb2)
618 router.Register(image_pb2)
619 router.Register(metadata_pb2)
Lizzy Preslande723c012022-06-10 05:05:37 +0000620 router.Register(observability_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600621 router.Register(packages_pb2)
622 router.Register(payload_pb2)
623 router.Register(portage_explorer_pb2)
624 router.Register(sdk_pb2)
Trent Aptedbd3cb412023-08-18 11:37:02 +1000625 router.Register(sdk_subtools_pb2)
Alex Klein1699fab2022-09-08 08:46:06 -0600626 router.Register(sysroot_pb2)
627 router.Register(test_pb2)
628 router.Register(toolchain_pb2)
629 logging.debug("Services registered successfully.")
Alex Klein146d4772019-06-20 13:48:25 -0600630
631
632def GetRouter():
Alex Klein54c891a2023-01-24 10:45:41 -0700633 """Get a router that has had all the services registered."""
Alex Klein1699fab2022-09-08 08:46:06 -0600634 router = Router()
635 RegisterServices(router)
Alex Klein146d4772019-06-20 13:48:25 -0600636
Alex Klein1699fab2022-09-08 08:46:06 -0600637 return router