| # Copyright 2022 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Utility script to fetch the requests from a builder.""" | |
| import csv | |
| import logging | |
| from pathlib import Path | |
| import re | |
| from chromite.third_party.google.protobuf import text_format | |
| from chromite.api import message_util | |
| from chromite.api import router | |
| from chromite.api.gen.analysis_service import analysis_service_pb2 | |
| from chromite.lib import commandline | |
| from chromite.lib import cros_build_lib | |
| from chromite.lib import osutils | |
| _COLUMNS = [ | |
| "step_name", | |
| "install_packages_request", | |
| "bundle_request", | |
| "bundle_vm_files_request", | |
| "binhost_get_request", | |
| "acl_args_request", | |
| "prepare_binhost_uploads_request", | |
| "set_binhost_request", | |
| ] | |
| def _camel_to_snake(string): | |
| # Transform FooBARBaz into FooBAR_Baz. Avoids making Foo_B_A_R_Baz. | |
| sub1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", string) | |
| # Transform FooBAR_Baz into Foo_BAR_Baz. | |
| sub2 = re.sub("([a-z0-9])([A-Z])", r"\1_\2", sub1) | |
| # Lower case to get foo_bar_baz. | |
| return sub2.lower() | |
| def _get_query(fields, build_id): | |
| return f""" | |
| SELECT {','.join(fields)} | |
| FROM | |
| chromeos_ci_eng.analysis_event_log.all | |
| WHERE build_id = {build_id}; | |
| """ | |
| def GetParser(): | |
| """Build the argument parser.""" | |
| parser = commandline.ArgumentParser(description=__doc__) | |
| parser.add_argument("build_id", type=int, help="The Build Bucket Build ID.") | |
| parser.add_argument( | |
| "--raw", | |
| default=False, | |
| action="store_true", | |
| help="Do not do any processing of field values before writing the " | |
| "input.", | |
| ) | |
| return parser | |
| def _ParseArguments(argv): | |
| """Parse and validate arguments.""" | |
| parser = GetParser() | |
| opts = parser.parse_args(argv) | |
| opts.Freeze() | |
| return opts | |
| def main(argv): | |
| opts = _ParseArguments(argv) | |
| analysis_service = analysis_service_pb2.AnalysisServiceEvent() | |
| request_one_of = analysis_service.DESCRIPTOR.oneofs_by_name["request"] | |
| fields = ["step_name"] + [x.name for x in request_one_of.fields] | |
| fields.remove("test_all_firmware_request") | |
| result = None | |
| unknown_field_re = re.compile(r"Unrecognized name: (?P<unknown_field>\w+)") | |
| while not result: | |
| query = _get_query(fields, opts.build_id) | |
| try: | |
| result = cros_build_lib.run( | |
| ["dremel", "--min_completion_ratio", "1", "--output", "csv"], | |
| input=query, | |
| capture_output=True, | |
| encoding="utf-8", | |
| ) | |
| except cros_build_lib.RunCommandError as e: | |
| m = unknown_field_re.search(str(e)) | |
| if m and m.group("unknown_field"): | |
| fields.remove(m.group("unknown_field")) | |
| else: | |
| raise | |
| router_obj = router.Router() | |
| router.RegisterServices(router_obj) | |
| serializer = message_util.JsonSerializer() | |
| endpoint_re = re.compile(r"(chromite\.api\.\w+/\w+)") | |
| reader = csv.DictReader(result.stdout.split("\r\n")) | |
| for row in reader: | |
| # Each row will have a step_name, and all the requests, but only one | |
| # request will actually be populated. | |
| m = endpoint_re.search(row["step_name"]) | |
| if not m: | |
| logging.warning("No endpoint identified in %s", row["step_name"]) | |
| continue | |
| endpoint = m.groups()[0] | |
| request = router_obj.get_input_message_instance(*endpoint.split("/")) | |
| for k, v in row.items(): | |
| if k == "step_name" or not v or v == "NULL": | |
| # Wrong column, or empty request. | |
| continue | |
| # Strip {} from around the message to account for extra layer of | |
| # nesting when the analysis service serializes it. | |
| text_format.Parse(v[1:-1], request) | |
| # Figure out which input.json needs to be written. | |
| method = _camel_to_snake(endpoint.split("/")[1]) | |
| service = _camel_to_snake( | |
| endpoint.split("/")[0].split(".")[2].replace("Service", "") | |
| ) | |
| file_name = f"{service}__{method}_input.json" | |
| if not opts.raw: | |
| # Dump chroot's directory fields since they're definitely going | |
| # to be builder paths that aren't usable. | |
| if request.HasField("chroot"): | |
| request.chroot.path = "" | |
| request.chroot.chrome_dir = "" | |
| request.chroot.cache_dir = "" | |
| contrib = Path(__file__).absolute().parent | |
| file_location = contrib / "call_scripts" / file_name | |
| osutils.WriteFile(file_location, serializer.serialize(request)) | |
| logging.notice(f"Wrote {file_name}") | |
| break |