1*c2e18aaaSAndroid Build Coastguard Worker# Copyright (C) 2024 The Android Open Source Project 2*c2e18aaaSAndroid Build Coastguard Worker# 3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*c2e18aaaSAndroid Build Coastguard Worker# 7*c2e18aaaSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*c2e18aaaSAndroid Build Coastguard Worker# 9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License. 14*c2e18aaaSAndroid Build Coastguard Worker 15*c2e18aaaSAndroid Build Coastguard Worker 16*c2e18aaaSAndroid Build Coastguard Worker"""A module for background python log artifacts uploading.""" 17*c2e18aaaSAndroid Build Coastguard Worker 18*c2e18aaaSAndroid Build Coastguard Workerimport argparse 19*c2e18aaaSAndroid Build Coastguard Workerimport functools 20*c2e18aaaSAndroid Build Coastguard Workerfrom importlib import resources 21*c2e18aaaSAndroid Build Coastguard Workerimport logging 22*c2e18aaaSAndroid Build Coastguard Workerimport multiprocessing 23*c2e18aaaSAndroid Build Coastguard Workerimport os 24*c2e18aaaSAndroid Build Coastguard Workerimport pathlib 25*c2e18aaaSAndroid Build Coastguard Workerimport subprocess 26*c2e18aaaSAndroid Build Coastguard Workerimport sys 27*c2e18aaaSAndroid Build Coastguard Workerfrom typing import Callable 28*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants 29*c2e18aaaSAndroid Build Coastguard Workerfrom atest.logstorage import logstorage_utils 30*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics 31*c2e18aaaSAndroid Build Coastguard Workerfrom googleapiclient import errors 32*c2e18aaaSAndroid Build Coastguard Workerfrom googleapiclient import http 33*c2e18aaaSAndroid Build Coastguard Worker 34*c2e18aaaSAndroid Build Coastguard Worker 35*c2e18aaaSAndroid Build Coastguard Worker_ENABLE_ATEST_LOG_UPLOADING_ENV_KEY = 'ENABLE_ATEST_LOG_UPLOADING' 36*c2e18aaaSAndroid Build Coastguard Worker 37*c2e18aaaSAndroid Build Coastguard Worker 38*c2e18aaaSAndroid Build Coastguard Workerclass _SimpleUploadingClient: 39*c2e18aaaSAndroid Build Coastguard Worker """A proxy class used to interact with the logstorage_utils module.""" 40*c2e18aaaSAndroid Build Coastguard Worker 41*c2e18aaaSAndroid Build Coastguard Worker def __init__(self, atest_run_id: str): 42*c2e18aaaSAndroid Build Coastguard Worker self._atest_run_id = atest_run_id 43*c2e18aaaSAndroid Build Coastguard Worker self._client = None 44*c2e18aaaSAndroid Build Coastguard Worker self._client_legacy = None 45*c2e18aaaSAndroid Build Coastguard Worker self._invocation_id = None 46*c2e18aaaSAndroid Build Coastguard Worker self._workunit_id = None 47*c2e18aaaSAndroid Build Coastguard Worker self._legacy_test_result_id = None 48*c2e18aaaSAndroid Build Coastguard Worker self._invocation_data = None 49*c2e18aaaSAndroid Build Coastguard Worker 50*c2e18aaaSAndroid Build Coastguard Worker def initialize_invocation(self): 51*c2e18aaaSAndroid Build Coastguard Worker """Initialize internal build clients and get invocation ID from AnTS.""" 52*c2e18aaaSAndroid Build Coastguard Worker configuration = {} 53*c2e18aaaSAndroid Build Coastguard Worker creds, self._invocation_data = logstorage_utils.do_upload_flow( 54*c2e18aaaSAndroid Build Coastguard Worker configuration, {'atest_run_id': self._atest_run_id} 55*c2e18aaaSAndroid Build Coastguard Worker ) 56*c2e18aaaSAndroid Build Coastguard Worker 57*c2e18aaaSAndroid Build Coastguard Worker self._client = logstorage_utils.BuildClient(creds) 58*c2e18aaaSAndroid Build Coastguard Worker # Legacy test result ID is required when using AnTS' `testartifact` API 59*c2e18aaaSAndroid Build Coastguard Worker # to upload test artifacts due to a limitation in the API, and we need 60*c2e18aaaSAndroid Build Coastguard Worker # The legacy client to get the legacy ID. 61*c2e18aaaSAndroid Build Coastguard Worker self._client_legacy = logstorage_utils.BuildClient( 62*c2e18aaaSAndroid Build Coastguard Worker creds, 63*c2e18aaaSAndroid Build Coastguard Worker api_version=constants.STORAGE_API_VERSION_LEGACY, 64*c2e18aaaSAndroid Build Coastguard Worker url=constants.DISCOVERY_SERVICE_LEGACY, 65*c2e18aaaSAndroid Build Coastguard Worker ) 66*c2e18aaaSAndroid Build Coastguard Worker 67*c2e18aaaSAndroid Build Coastguard Worker self._invocation_id = configuration[constants.INVOCATION_ID] 68*c2e18aaaSAndroid Build Coastguard Worker self._workunit_id = configuration[constants.WORKUNIT_ID] 69*c2e18aaaSAndroid Build Coastguard Worker 70*c2e18aaaSAndroid Build Coastguard Worker self._legacy_test_result_id = ( 71*c2e18aaaSAndroid Build Coastguard Worker self._client_legacy.client.testresult() 72*c2e18aaaSAndroid Build Coastguard Worker .insert( 73*c2e18aaaSAndroid Build Coastguard Worker buildId=self._invocation_data['primaryBuild']['buildId'], 74*c2e18aaaSAndroid Build Coastguard Worker target=self._invocation_data['primaryBuild']['buildTarget'], 75*c2e18aaaSAndroid Build Coastguard Worker attemptId='latest', 76*c2e18aaaSAndroid Build Coastguard Worker body={ 77*c2e18aaaSAndroid Build Coastguard Worker 'status': 'completePass', 78*c2e18aaaSAndroid Build Coastguard Worker }, 79*c2e18aaaSAndroid Build Coastguard Worker ) 80*c2e18aaaSAndroid Build Coastguard Worker .execute()['id'] 81*c2e18aaaSAndroid Build Coastguard Worker ) 82*c2e18aaaSAndroid Build Coastguard Worker 83*c2e18aaaSAndroid Build Coastguard Worker logging.debug( 84*c2e18aaaSAndroid Build Coastguard Worker 'Initialized AnTS invocation: http://ab/%s', self._invocation_id 85*c2e18aaaSAndroid Build Coastguard Worker ) 86*c2e18aaaSAndroid Build Coastguard Worker 87*c2e18aaaSAndroid Build Coastguard Worker def complete_invocation(self) -> None: 88*c2e18aaaSAndroid Build Coastguard Worker """Set schedule state as complete to AnTS for the current invocation.""" 89*c2e18aaaSAndroid Build Coastguard Worker self._invocation_data['schedulerState'] = 'completed' 90*c2e18aaaSAndroid Build Coastguard Worker self._client.update_invocation(self._invocation_data) 91*c2e18aaaSAndroid Build Coastguard Worker logging.debug( 92*c2e18aaaSAndroid Build Coastguard Worker 'Finalized AnTS invocation: http://ab/%s', self._invocation_id 93*c2e18aaaSAndroid Build Coastguard Worker ) 94*c2e18aaaSAndroid Build Coastguard Worker 95*c2e18aaaSAndroid Build Coastguard Worker def upload_artifact( 96*c2e18aaaSAndroid Build Coastguard Worker self, 97*c2e18aaaSAndroid Build Coastguard Worker resource_id: str, 98*c2e18aaaSAndroid Build Coastguard Worker metadata: dict[str, str], 99*c2e18aaaSAndroid Build Coastguard Worker artifact_path: pathlib.Path, 100*c2e18aaaSAndroid Build Coastguard Worker num_of_retries, 101*c2e18aaaSAndroid Build Coastguard Worker ) -> None: 102*c2e18aaaSAndroid Build Coastguard Worker """Upload an artifact to AnTS with retries. 103*c2e18aaaSAndroid Build Coastguard Worker 104*c2e18aaaSAndroid Build Coastguard Worker Args: 105*c2e18aaaSAndroid Build Coastguard Worker resource_id: The artifact's destination resource ID 106*c2e18aaaSAndroid Build Coastguard Worker metadata: The metadata for the artifact. Invocation ID and work unit ID 107*c2e18aaaSAndroid Build Coastguard Worker is not required in the input metadata dict as this method will add the 108*c2e18aaaSAndroid Build Coastguard Worker values to it. 109*c2e18aaaSAndroid Build Coastguard Worker artifact_path: The path of the artifact file 110*c2e18aaaSAndroid Build Coastguard Worker num_of_retries: Number of retries when the upload request failed 111*c2e18aaaSAndroid Build Coastguard Worker 112*c2e18aaaSAndroid Build Coastguard Worker Raises: 113*c2e18aaaSAndroid Build Coastguard Worker errors.HttpError: When the upload failed. 114*c2e18aaaSAndroid Build Coastguard Worker """ 115*c2e18aaaSAndroid Build Coastguard Worker metadata['invocationId'] = self._invocation_id 116*c2e18aaaSAndroid Build Coastguard Worker metadata['workUnitId'] = self._workunit_id 117*c2e18aaaSAndroid Build Coastguard Worker 118*c2e18aaaSAndroid Build Coastguard Worker self._client.client.testartifact().update( 119*c2e18aaaSAndroid Build Coastguard Worker resourceId=resource_id, 120*c2e18aaaSAndroid Build Coastguard Worker invocationId=self._invocation_id, 121*c2e18aaaSAndroid Build Coastguard Worker workUnitId=self._workunit_id, 122*c2e18aaaSAndroid Build Coastguard Worker body=metadata, 123*c2e18aaaSAndroid Build Coastguard Worker legacyTestResultId=self._legacy_test_result_id, 124*c2e18aaaSAndroid Build Coastguard Worker media_body=http.MediaFileUpload(artifact_path), 125*c2e18aaaSAndroid Build Coastguard Worker ).execute(num_retries=num_of_retries) 126*c2e18aaaSAndroid Build Coastguard Worker 127*c2e18aaaSAndroid Build Coastguard Worker 128*c2e18aaaSAndroid Build Coastguard Workerclass _LogUploadSession: 129*c2e18aaaSAndroid Build Coastguard Worker """A class to handle log uploading to AnTS.""" 130*c2e18aaaSAndroid Build Coastguard Worker 131*c2e18aaaSAndroid Build Coastguard Worker def __init__( 132*c2e18aaaSAndroid Build Coastguard Worker self, atest_run_id: str, upload_client: _SimpleUploadingClient = None 133*c2e18aaaSAndroid Build Coastguard Worker ): 134*c2e18aaaSAndroid Build Coastguard Worker self._upload_client = upload_client or _SimpleUploadingClient(atest_run_id) 135*c2e18aaaSAndroid Build Coastguard Worker self._resource_ids = {} 136*c2e18aaaSAndroid Build Coastguard Worker 137*c2e18aaaSAndroid Build Coastguard Worker def __enter__(self): 138*c2e18aaaSAndroid Build Coastguard Worker self._upload_client.initialize_invocation() 139*c2e18aaaSAndroid Build Coastguard Worker return self 140*c2e18aaaSAndroid Build Coastguard Worker 141*c2e18aaaSAndroid Build Coastguard Worker def __exit__(self, exc_type, exc_val, exc_tb): 142*c2e18aaaSAndroid Build Coastguard Worker self._upload_client.complete_invocation() 143*c2e18aaaSAndroid Build Coastguard Worker 144*c2e18aaaSAndroid Build Coastguard Worker @classmethod 145*c2e18aaaSAndroid Build Coastguard Worker def _get_file_paths(cls, directory: pathlib.Path) -> list[pathlib.Path]: 146*c2e18aaaSAndroid Build Coastguard Worker """Returns all the files under the given directory following symbolic links. 147*c2e18aaaSAndroid Build Coastguard Worker 148*c2e18aaaSAndroid Build Coastguard Worker Args: 149*c2e18aaaSAndroid Build Coastguard Worker directory: The root directory path. 150*c2e18aaaSAndroid Build Coastguard Worker 151*c2e18aaaSAndroid Build Coastguard Worker Returns: 152*c2e18aaaSAndroid Build Coastguard Worker A list of pathlib.Path objects representing the file paths. 153*c2e18aaaSAndroid Build Coastguard Worker """ 154*c2e18aaaSAndroid Build Coastguard Worker 155*c2e18aaaSAndroid Build Coastguard Worker file_paths = [] 156*c2e18aaaSAndroid Build Coastguard Worker with os.scandir(directory) as scan: 157*c2e18aaaSAndroid Build Coastguard Worker for entry in scan: 158*c2e18aaaSAndroid Build Coastguard Worker if entry.is_file(): 159*c2e18aaaSAndroid Build Coastguard Worker file_paths.append(pathlib.Path(entry.path)) 160*c2e18aaaSAndroid Build Coastguard Worker elif entry.is_dir(): 161*c2e18aaaSAndroid Build Coastguard Worker file_paths.extend(cls._get_file_paths(entry)) 162*c2e18aaaSAndroid Build Coastguard Worker 163*c2e18aaaSAndroid Build Coastguard Worker return file_paths 164*c2e18aaaSAndroid Build Coastguard Worker 165*c2e18aaaSAndroid Build Coastguard Worker @staticmethod 166*c2e18aaaSAndroid Build Coastguard Worker def _create_artifact_metadata(artifact_path: pathlib.Path) -> dict[str, str]: 167*c2e18aaaSAndroid Build Coastguard Worker metadata = { 168*c2e18aaaSAndroid Build Coastguard Worker 'name': artifact_path.name, 169*c2e18aaaSAndroid Build Coastguard Worker } 170*c2e18aaaSAndroid Build Coastguard Worker if artifact_path.suffix in ['.txt', '.log']: 171*c2e18aaaSAndroid Build Coastguard Worker metadata['artifactType'] = 'HOST_LOG' 172*c2e18aaaSAndroid Build Coastguard Worker metadata['contentType'] = 'text/plain' 173*c2e18aaaSAndroid Build Coastguard Worker return metadata 174*c2e18aaaSAndroid Build Coastguard Worker 175*c2e18aaaSAndroid Build Coastguard Worker def upload_directory(self, artifacts_dir: pathlib.Path) -> None: 176*c2e18aaaSAndroid Build Coastguard Worker """Upload all artifacts under a directory.""" 177*c2e18aaaSAndroid Build Coastguard Worker logging.debug('Uploading artifact directory %s', artifacts_dir) 178*c2e18aaaSAndroid Build Coastguard Worker for artifact_path in self._get_file_paths(artifacts_dir): 179*c2e18aaaSAndroid Build Coastguard Worker self.upload_single_file(artifact_path) 180*c2e18aaaSAndroid Build Coastguard Worker 181*c2e18aaaSAndroid Build Coastguard Worker def upload_single_file(self, artifact_path: pathlib.Path) -> None: 182*c2e18aaaSAndroid Build Coastguard Worker """Upload an single artifact.""" 183*c2e18aaaSAndroid Build Coastguard Worker logging.debug('Uploading artifact path %s', artifact_path) 184*c2e18aaaSAndroid Build Coastguard Worker file_upload_retires = 3 185*c2e18aaaSAndroid Build Coastguard Worker try: 186*c2e18aaaSAndroid Build Coastguard Worker self._upload_client.upload_artifact( 187*c2e18aaaSAndroid Build Coastguard Worker self._create_resource_id(artifact_path), 188*c2e18aaaSAndroid Build Coastguard Worker self._create_artifact_metadata(artifact_path), 189*c2e18aaaSAndroid Build Coastguard Worker artifact_path, 190*c2e18aaaSAndroid Build Coastguard Worker file_upload_retires, 191*c2e18aaaSAndroid Build Coastguard Worker ) 192*c2e18aaaSAndroid Build Coastguard Worker except errors.HttpError as e: 193*c2e18aaaSAndroid Build Coastguard Worker # Upload error may happen due to temporary network issue. We log down 194*c2e18aaaSAndroid Build Coastguard Worker # an error but do stop the upload loop so that other files may gets 195*c2e18aaaSAndroid Build Coastguard Worker # uploaded when the network recover. 196*c2e18aaaSAndroid Build Coastguard Worker logging.error('Failed to upload file %s with error: %s', artifact_path, e) 197*c2e18aaaSAndroid Build Coastguard Worker 198*c2e18aaaSAndroid Build Coastguard Worker def _create_resource_id(self, artifact_path: pathlib.Path) -> str: 199*c2e18aaaSAndroid Build Coastguard Worker """Create a unique resource id for a file. 200*c2e18aaaSAndroid Build Coastguard Worker 201*c2e18aaaSAndroid Build Coastguard Worker Args: 202*c2e18aaaSAndroid Build Coastguard Worker artifact_path: artifact file path 203*c2e18aaaSAndroid Build Coastguard Worker 204*c2e18aaaSAndroid Build Coastguard Worker Returns: 205*c2e18aaaSAndroid Build Coastguard Worker A unique resource ID derived from the file name. If the file name 206*c2e18aaaSAndroid Build Coastguard Worker has appeared before, an extra string will be inserted between the file 207*c2e18aaaSAndroid Build Coastguard Worker name stem and suffix to make it unique. 208*c2e18aaaSAndroid Build Coastguard Worker """ 209*c2e18aaaSAndroid Build Coastguard Worker count = self._resource_ids.get(artifact_path.name, 0) + 1 210*c2e18aaaSAndroid Build Coastguard Worker self._resource_ids[artifact_path.name] = count 211*c2e18aaaSAndroid Build Coastguard Worker return ( 212*c2e18aaaSAndroid Build Coastguard Worker artifact_path.name 213*c2e18aaaSAndroid Build Coastguard Worker if count == 1 214*c2e18aaaSAndroid Build Coastguard Worker else f'{artifact_path.stem}_{count}{artifact_path.suffix}' 215*c2e18aaaSAndroid Build Coastguard Worker ) 216*c2e18aaaSAndroid Build Coastguard Worker 217*c2e18aaaSAndroid Build Coastguard Worker 218*c2e18aaaSAndroid Build Coastguard Worker@functools.cache 219*c2e18aaaSAndroid Build Coastguard Workerdef is_uploading_logs(gcert_checker: Callable[[], bool] = None) -> bool: 220*c2e18aaaSAndroid Build Coastguard Worker """Determines whether log uploading is happening in the current run.""" 221*c2e18aaaSAndroid Build Coastguard Worker if os.environ.get(_ENABLE_ATEST_LOG_UPLOADING_ENV_KEY, 'true').lower() in [ 222*c2e18aaaSAndroid Build Coastguard Worker 'false', 223*c2e18aaaSAndroid Build Coastguard Worker '0', 224*c2e18aaaSAndroid Build Coastguard Worker ]: 225*c2e18aaaSAndroid Build Coastguard Worker return False 226*c2e18aaaSAndroid Build Coastguard Worker 227*c2e18aaaSAndroid Build Coastguard Worker if not logstorage_utils.is_credential_available(): 228*c2e18aaaSAndroid Build Coastguard Worker return False 229*c2e18aaaSAndroid Build Coastguard Worker 230*c2e18aaaSAndroid Build Coastguard Worker # Checks whether gcert is available and not about to expire. 231*c2e18aaaSAndroid Build Coastguard Worker if gcert_checker is None: 232*c2e18aaaSAndroid Build Coastguard Worker gcert_checker = ( 233*c2e18aaaSAndroid Build Coastguard Worker lambda: subprocess.run( 234*c2e18aaaSAndroid Build Coastguard Worker ['which', 'gcertstatus'], 235*c2e18aaaSAndroid Build Coastguard Worker capture_output=True, 236*c2e18aaaSAndroid Build Coastguard Worker check=False, 237*c2e18aaaSAndroid Build Coastguard Worker ).returncode 238*c2e18aaaSAndroid Build Coastguard Worker == 0 239*c2e18aaaSAndroid Build Coastguard Worker and subprocess.run( 240*c2e18aaaSAndroid Build Coastguard Worker ['gcertstatus', '--check_remaining=6m'], 241*c2e18aaaSAndroid Build Coastguard Worker capture_output=True, 242*c2e18aaaSAndroid Build Coastguard Worker check=False, 243*c2e18aaaSAndroid Build Coastguard Worker ).returncode 244*c2e18aaaSAndroid Build Coastguard Worker == 0 245*c2e18aaaSAndroid Build Coastguard Worker ) 246*c2e18aaaSAndroid Build Coastguard Worker return gcert_checker() 247*c2e18aaaSAndroid Build Coastguard Worker 248*c2e18aaaSAndroid Build Coastguard Worker 249*c2e18aaaSAndroid Build Coastguard Workerdef upload_logs_detached(logs_dir: pathlib.Path): 250*c2e18aaaSAndroid Build Coastguard Worker """Upload logs to AnTS in a detached process.""" 251*c2e18aaaSAndroid Build Coastguard Worker if not is_uploading_logs(): 252*c2e18aaaSAndroid Build Coastguard Worker return 253*c2e18aaaSAndroid Build Coastguard Worker 254*c2e18aaaSAndroid Build Coastguard Worker assert logs_dir, 'artifacts_dir cannot be None.' 255*c2e18aaaSAndroid Build Coastguard Worker assert logs_dir.as_posix(), 'The path of artifacts_dir should not be empty.' 256*c2e18aaaSAndroid Build Coastguard Worker 257*c2e18aaaSAndroid Build Coastguard Worker def _start_upload_process(): 258*c2e18aaaSAndroid Build Coastguard Worker # We need to fock a background process instead of calling Popen with 259*c2e18aaaSAndroid Build Coastguard Worker # start_new_session=True because we want to make sure the atest_log_uploader 260*c2e18aaaSAndroid Build Coastguard Worker # resource binary is deleted after execution. 261*c2e18aaaSAndroid Build Coastguard Worker if os.fork() != 0: 262*c2e18aaaSAndroid Build Coastguard Worker return 263*c2e18aaaSAndroid Build Coastguard Worker with resources.as_file( 264*c2e18aaaSAndroid Build Coastguard Worker resources.files('atest').joinpath('atest_log_uploader') 265*c2e18aaaSAndroid Build Coastguard Worker ) as uploader_path: 266*c2e18aaaSAndroid Build Coastguard Worker # TODO: Explore whether it's possible to package the binary with 267*c2e18aaaSAndroid Build Coastguard Worker # executable permission. 268*c2e18aaaSAndroid Build Coastguard Worker os.chmod(uploader_path, 0o755) 269*c2e18aaaSAndroid Build Coastguard Worker 270*c2e18aaaSAndroid Build Coastguard Worker timeout = 60 * 60 * 24 # 1 day 271*c2e18aaaSAndroid Build Coastguard Worker # We need to call atest_log_uploader as a binary so that the python 272*c2e18aaaSAndroid Build Coastguard Worker # environment can be properly loaded. 273*c2e18aaaSAndroid Build Coastguard Worker process = subprocess.run( 274*c2e18aaaSAndroid Build Coastguard Worker [uploader_path.as_posix(), logs_dir.as_posix(), metrics.get_run_id()], 275*c2e18aaaSAndroid Build Coastguard Worker timeout=timeout, 276*c2e18aaaSAndroid Build Coastguard Worker capture_output=True, 277*c2e18aaaSAndroid Build Coastguard Worker check=False, 278*c2e18aaaSAndroid Build Coastguard Worker ) 279*c2e18aaaSAndroid Build Coastguard Worker if process.returncode: 280*c2e18aaaSAndroid Build Coastguard Worker logging.error('Failed to run log upload process: %s', process) 281*c2e18aaaSAndroid Build Coastguard Worker 282*c2e18aaaSAndroid Build Coastguard Worker proc = multiprocessing.Process(target=_start_upload_process) 283*c2e18aaaSAndroid Build Coastguard Worker proc.start() 284*c2e18aaaSAndroid Build Coastguard Worker proc.join() 285*c2e18aaaSAndroid Build Coastguard Worker 286*c2e18aaaSAndroid Build Coastguard Worker 287*c2e18aaaSAndroid Build Coastguard Workerdef _configure_logging(log_dir: str) -> None: 288*c2e18aaaSAndroid Build Coastguard Worker """Configure the logger.""" 289*c2e18aaaSAndroid Build Coastguard Worker log_fmat = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s' 290*c2e18aaaSAndroid Build Coastguard Worker date_fmt = '%Y-%m-%d %H:%M:%S' 291*c2e18aaaSAndroid Build Coastguard Worker log_path = os.path.join(log_dir, 'atest_log_uploader.log') 292*c2e18aaaSAndroid Build Coastguard Worker logging.getLogger('').handlers = [] 293*c2e18aaaSAndroid Build Coastguard Worker logging.basicConfig( 294*c2e18aaaSAndroid Build Coastguard Worker filename=log_path, level=logging.DEBUG, format=log_fmat, datefmt=date_fmt 295*c2e18aaaSAndroid Build Coastguard Worker ) 296*c2e18aaaSAndroid Build Coastguard Worker 297*c2e18aaaSAndroid Build Coastguard Worker 298*c2e18aaaSAndroid Build Coastguard Workerdef _redirect_stdout_stderr() -> None: 299*c2e18aaaSAndroid Build Coastguard Worker """Redirect stdout and stderr to logger.""" 300*c2e18aaaSAndroid Build Coastguard Worker 301*c2e18aaaSAndroid Build Coastguard Worker class _StreamToLogger: 302*c2e18aaaSAndroid Build Coastguard Worker 303*c2e18aaaSAndroid Build Coastguard Worker def __init__(self, logger, log_level=logging.INFO): 304*c2e18aaaSAndroid Build Coastguard Worker self._logger = logger 305*c2e18aaaSAndroid Build Coastguard Worker self._log_level = log_level 306*c2e18aaaSAndroid Build Coastguard Worker 307*c2e18aaaSAndroid Build Coastguard Worker def write(self, buf): 308*c2e18aaaSAndroid Build Coastguard Worker self._logger.log(self._log_level, buf) 309*c2e18aaaSAndroid Build Coastguard Worker 310*c2e18aaaSAndroid Build Coastguard Worker def flush(self): 311*c2e18aaaSAndroid Build Coastguard Worker pass 312*c2e18aaaSAndroid Build Coastguard Worker 313*c2e18aaaSAndroid Build Coastguard Worker logger = logging.getLogger('') 314*c2e18aaaSAndroid Build Coastguard Worker sys.stdout = _StreamToLogger(logger, logging.INFO) 315*c2e18aaaSAndroid Build Coastguard Worker sys.stderr = _StreamToLogger(logger, logging.ERROR) 316*c2e18aaaSAndroid Build Coastguard Worker 317*c2e18aaaSAndroid Build Coastguard Worker 318*c2e18aaaSAndroid Build Coastguard Workerdef _main() -> None: 319*c2e18aaaSAndroid Build Coastguard Worker """The main method to be executed when executing this module as a binary.""" 320*c2e18aaaSAndroid Build Coastguard Worker arg_parser = argparse.ArgumentParser( 321*c2e18aaaSAndroid Build Coastguard Worker description='Internal tool for uploading test artifacts to AnTS.', 322*c2e18aaaSAndroid Build Coastguard Worker add_help=True, 323*c2e18aaaSAndroid Build Coastguard Worker ) 324*c2e18aaaSAndroid Build Coastguard Worker arg_parser.add_argument( 325*c2e18aaaSAndroid Build Coastguard Worker 'artifacts_dir', help='Root directory of the test artifacts.' 326*c2e18aaaSAndroid Build Coastguard Worker ) 327*c2e18aaaSAndroid Build Coastguard Worker arg_parser.add_argument('atest_run_id', help='The Atest run ID.') 328*c2e18aaaSAndroid Build Coastguard Worker args = arg_parser.parse_args() 329*c2e18aaaSAndroid Build Coastguard Worker _configure_logging(args.artifacts_dir) 330*c2e18aaaSAndroid Build Coastguard Worker _redirect_stdout_stderr() 331*c2e18aaaSAndroid Build Coastguard Worker 332*c2e18aaaSAndroid Build Coastguard Worker with _LogUploadSession(args.atest_run_id) as artifact_upload_session: 333*c2e18aaaSAndroid Build Coastguard Worker artifact_upload_session.upload_directory(pathlib.Path(args.artifacts_dir)) 334*c2e18aaaSAndroid Build Coastguard Worker 335*c2e18aaaSAndroid Build Coastguard Worker 336*c2e18aaaSAndroid Build Coastguard Workerif __name__ == '__main__': 337*c2e18aaaSAndroid Build Coastguard Worker _main() 338