xref: /aosp_15_r20/tools/asuite/atest/logstorage/log_uploader.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
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