Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / stages / generic_stages.py
1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Module containing the generic stages."""
6
7 from __future__ import print_function
8
9 import contextlib
10 import fnmatch
11 import json
12 import os
13 import re
14 import sys
15 import time
16 import traceback
17
18 # We import mox so that we can identify mox exceptions and pass them through
19 # in our exception handling code.
20 try:
21   # pylint: disable=F0401
22   import mox
23 except ImportError:
24   mox = None
25
26 from chromite.cbuildbot import cbuildbot_config
27 from chromite.cbuildbot import commands
28 from chromite.cbuildbot import failures_lib
29 from chromite.cbuildbot import results_lib
30 from chromite.cbuildbot import constants
31 from chromite.cbuildbot import repository
32 from chromite.lib import cidb
33 from chromite.lib import cros_build_lib
34 from chromite.lib import gs
35 from chromite.lib import osutils
36 from chromite.lib import parallel
37 from chromite.lib import portage_util
38 from chromite.lib import retry_util
39 from chromite.lib import timeout_util
40
41
42 class BuilderStage(object):
43   """Parent class for stages to be performed by a builder."""
44   # Used to remove 'Stage' suffix of stage class when generating stage name.
45   name_stage_re = re.compile(r'(\w+)Stage')
46
47   # TODO(sosa): Remove these once we have a SEND/RECIEVE IPC mechanism
48   # implemented.
49   overlays = None
50   push_overlays = None
51
52   # Class should set this if they have a corresponding no<stage> option that
53   # skips their stage.
54   # TODO(mtennant): Rename this something like skip_option_name.
55   option_name = None
56
57   # Class should set this if they have a corresponding setting in
58   # the build_config that skips their stage.
59   # TODO(mtennant): Rename this something like skip_config_name.
60   config_name = None
61
62   @classmethod
63   def StageNamePrefix(cls):
64     """Return cls.__name__ with any 'Stage' suffix removed."""
65     match = cls.name_stage_re.match(cls.__name__)
66     assert match, 'Class name %s does not end with Stage' % cls.__name__
67     return match.group(1)
68
69   def __init__(self, builder_run, suffix=None, attempt=None, max_retry=None):
70     """Create a builder stage.
71
72     Args:
73       builder_run: The BuilderRun object for the run this stage is part of.
74       suffix: The suffix to append to the buildbot name. Defaults to None.
75       attempt: If this build is to be retried, the current attempt number
76         (starting from 1). Defaults to None. Is only valid if |max_retry| is
77         also specified.
78       max_retry: The maximum number of retries. Defaults to None. Is only valid
79         if |attempt| is also specified.
80     """
81     self._run = builder_run
82
83     self._attempt = attempt
84     self._max_retry = max_retry
85
86     # Construct self.name, the name string for this stage instance.
87     self.name = self._prefix = self.StageNamePrefix()
88     if suffix:
89       self.name += suffix
90
91     # TODO(mtennant): Phase this out and use self._run.bot_id directly.
92     self._bot_id = self._run.bot_id
93
94     # self._boards holds list of boards involved in this run.
95     # TODO(mtennant): Replace self._boards with a self._run.boards?
96     self._boards = self._run.config.boards
97
98     # TODO(mtennant): Try to rely on just self._run.buildroot directly, if
99     # the os.path.abspath can be applied there instead.
100     self._build_root = os.path.abspath(self._run.buildroot)
101     self._prebuilt_type = None
102     if self._run.ShouldUploadPrebuilts():
103       self._prebuilt_type = self._run.config.build_type
104
105     # Determine correct chrome_rev.
106     self._chrome_rev = self._run.config.chrome_rev
107     if self._run.options.chrome_rev:
108       self._chrome_rev = self._run.options.chrome_rev
109
110     # USE and enviroment variable settings.
111     self._portage_extra_env = {}
112     useflags = self._run.config.useflags[:]
113
114     if self._run.options.clobber:
115       self._portage_extra_env['IGNORE_PREFLIGHT_BINHOST'] = '1'
116
117     if self._run.options.chrome_root:
118       self._portage_extra_env['CHROME_ORIGIN'] = 'LOCAL_SOURCE'
119
120     self._latest_toolchain = (self._run.config.latest_toolchain or
121                               self._run.options.latest_toolchain)
122     if self._latest_toolchain and self._run.config.gcc_githash:
123       useflags.append('git_gcc')
124       self._portage_extra_env['GCC_GITHASH'] = self._run.config.gcc_githash
125
126     if useflags:
127       self._portage_extra_env['USE'] = ' '.join(useflags)
128
129   def GetStageNames(self):
130     """Get a list of the places where this stage has recorded results."""
131     return [self.name]
132
133   # TODO(akeshet): Eliminate this method and update the callers to use
134   # builder run directly.
135   def ConstructDashboardURL(self, stage=None):
136     """Return the dashboard URL
137
138     This is the direct link to buildbot logs as seen in build.chromium.org
139
140     Args:
141       stage: Link to a specific |stage|, otherwise the general buildbot log
142
143     Returns:
144       The fully formed URL
145     """
146     return self._run.ConstructDashboardURL(stage=stage)
147
148   def _ExtractOverlays(self):
149     """Extracts list of overlays into class."""
150     overlays = portage_util.FindOverlays(
151         self._run.config.overlays, buildroot=self._build_root)
152     push_overlays = portage_util.FindOverlays(
153         self._run.config.push_overlays, buildroot=self._build_root)
154
155     # Sanity checks.
156     # We cannot push to overlays that we don't rev.
157     assert set(push_overlays).issubset(set(overlays))
158     # Either has to be a master or not have any push overlays.
159     assert self._run.config.master or not push_overlays
160
161     return overlays, push_overlays
162
163   def GetRepoRepository(self, **kwargs):
164     """Create a new repo repository object."""
165     manifest_url = self._run.options.manifest_repo_url
166     if manifest_url is None:
167       manifest_url = self._run.config.manifest_repo_url
168
169     kwargs.setdefault('referenced_repo', self._run.options.reference_repo)
170     kwargs.setdefault('branch', self._run.manifest_branch)
171     kwargs.setdefault('manifest', self._run.config.manifest)
172
173     return repository.RepoRepository(manifest_url, self._build_root, **kwargs)
174
175   def _Print(self, msg):
176     """Prints a msg to stderr."""
177     sys.stdout.flush()
178     print(msg, file=sys.stderr)
179     sys.stderr.flush()
180
181   def _PrintLoudly(self, msg):
182     """Prints a msg with loudly."""
183
184     border_line = '*' * 60
185     edge = '*' * 2
186
187     sys.stdout.flush()
188     print(border_line, file=sys.stderr)
189
190     msg_lines = msg.split('\n')
191
192     # If the last line is whitespace only drop it.
193     if not msg_lines[-1].rstrip():
194       del msg_lines[-1]
195
196     for msg_line in msg_lines:
197       print('%s %s' % (edge, msg_line), file=sys.stderr)
198
199     print(border_line, file=sys.stderr)
200     sys.stderr.flush()
201
202   def _GetPortageEnvVar(self, envvar, board):
203     """Get a portage environment variable for the configuration's board.
204
205     Args:
206       envvar: The environment variable to get. E.g. 'PORTAGE_BINHOST'.
207       board: The board to apply, if any.  Specify None to use host.
208
209     Returns:
210       The value of the environment variable, as a string. If no such variable
211       can be found, return the empty string.
212     """
213     cwd = os.path.join(self._build_root, 'src', 'scripts')
214     if board:
215       portageq = 'portageq-%s' % board
216     else:
217       portageq = 'portageq'
218     binhost = cros_build_lib.RunCommand(
219         [portageq, 'envvar', envvar], cwd=cwd, redirect_stdout=True,
220         enter_chroot=True, error_code_ok=True)
221     return binhost.output.rstrip('\n')
222
223   def _GetSlaveConfigs(self):
224     """Get the slave configs for the current build config.
225
226     This assumes self._run.config is a master config.
227
228     Returns:
229       A list of build configs corresponding to the slaves for the master
230         build config at self._run.config.
231
232     Raises:
233       See cbuildbot_config.GetSlavesForMaster for details.
234     """
235     return cbuildbot_config.GetSlavesForMaster(self._run.config)
236
237   def _Begin(self):
238     """Can be overridden.  Called before a stage is performed."""
239
240     # Tell the buildbot we are starting a new step for the waterfall
241     cros_build_lib.PrintBuildbotStepName(self.name)
242
243     self._PrintLoudly('Start Stage %s - %s\n\n%s' % (
244         self.name, cros_build_lib.UserDateTimeFormat(), self.__doc__))
245
246   def _Finish(self):
247     """Can be overridden.  Called after a stage has been performed."""
248     self._PrintLoudly('Finished Stage %s - %s' %
249                       (self.name, cros_build_lib.UserDateTimeFormat()))
250
251   def PerformStage(self):
252     """Subclassed stages must override this function to perform what they want
253     to be done.
254     """
255
256   def _HandleExceptionAsSuccess(self, _exc_info):
257     """Use instead of HandleStageException to ignore an exception."""
258     return results_lib.Results.SUCCESS, None
259
260   @staticmethod
261   def _StringifyException(exc_info):
262     """Convert an exception into a string.
263
264     Args:
265       exc_info: A (type, value, traceback) tuple as returned by sys.exc_info().
266
267     Returns:
268       A string description of the exception.
269     """
270     exc_type, exc_value = exc_info[:2]
271     if issubclass(exc_type, failures_lib.StepFailure):
272       return str(exc_value)
273     else:
274       return ''.join(traceback.format_exception(*exc_info))
275
276   @classmethod
277   def _HandleExceptionAsWarning(cls, exc_info, retrying=False):
278     """Use instead of HandleStageException to treat an exception as a warning.
279
280     This is used by the ForgivingBuilderStage's to treat any exceptions as
281     warnings instead of stage failures.
282     """
283     description = cls._StringifyException(exc_info)
284     cros_build_lib.PrintBuildbotStepWarnings()
285     cros_build_lib.Warning(description)
286     return (results_lib.Results.FORGIVEN, description, retrying)
287
288   @classmethod
289   def _HandleExceptionAsError(cls, exc_info):
290     """Handle an exception as an error, but ignore stage retry settings.
291
292     Meant as a helper for _HandleStageException code only.
293
294     Args:
295       exc_info: A (type, value, traceback) tuple as returned by sys.exc_info().
296
297     Returns:
298       Result tuple of (exception, description, retrying).
299     """
300     # Tell the user about the exception, and record it.
301     retrying = False
302     description = cls._StringifyException(exc_info)
303     cros_build_lib.PrintBuildbotStepFailure()
304     cros_build_lib.Error(description)
305     return (exc_info[1], description, retrying)
306
307   def _HandleStageException(self, exc_info):
308     """Called when PerformStage throws an exception.  Can be overriden.
309
310     Args:
311       exc_info: A (type, value, traceback) tuple as returned by sys.exc_info().
312
313     Returns:
314       Result tuple of (exception, description, retrying).  If it isn't an
315       exception, then description will be None.
316     """
317     if self._attempt and self._max_retry and self._attempt <= self._max_retry:
318       return self._HandleExceptionAsWarning(exc_info, retrying=True)
319     else:
320       return self._HandleExceptionAsError(exc_info)
321
322   def _TopHandleStageException(self):
323     """Called when PerformStage throws an unhandled exception.
324
325     Should only be called by the Run function.  Provides a wrapper around
326     _HandleStageException to handle buggy handlers.  We must go deeper...
327     """
328     exc_info = sys.exc_info()
329     try:
330       return self._HandleStageException(exc_info)
331     except Exception:
332       cros_build_lib.Error(
333           'An exception was thrown while running _HandleStageException')
334       cros_build_lib.Error('The original exception was:', exc_info=exc_info)
335       cros_build_lib.Error('The new exception is:', exc_info=True)
336       return self._HandleExceptionAsError(exc_info)
337
338   def HandleSkip(self):
339     """Run if the stage is skipped."""
340     pass
341
342   def _RecordResult(self, *args, **kwargs):
343     """Record a successful or failed result."""
344     results_lib.Results.Record(*args, **kwargs)
345
346   def Run(self):
347     """Have the builder execute the stage."""
348     # See if this stage should be skipped.
349     if (self.option_name and not getattr(self._run.options, self.option_name) or
350         self.config_name and not getattr(self._run.config, self.config_name)):
351       self._PrintLoudly('Not running Stage %s' % self.name)
352       self.HandleSkip()
353       self._RecordResult(self.name, results_lib.Results.SKIPPED,
354                          prefix=self._prefix)
355       return
356
357     record = results_lib.Results.PreviouslyCompletedRecord(self.name)
358     if record:
359       # Success is stored in the results log for a stage that completed
360       # successfully in a previous run.
361       self._PrintLoudly('Stage %s processed previously' % self.name)
362       self.HandleSkip()
363       self._RecordResult(self.name, results_lib.Results.SUCCESS,
364                          prefix=self._prefix, board=record.board,
365                          time=float(record.time))
366       return
367
368     start_time = time.time()
369
370     # Set default values
371     result = results_lib.Results.SUCCESS
372     description = None
373
374     sys.stdout.flush()
375     sys.stderr.flush()
376     self._Begin()
377     try:
378       # TODO(davidjames): Verify that PerformStage always returns None. See
379       # crbug.com/264781
380       self.PerformStage()
381     except SystemExit as e:
382       if e.code != 0:
383         result, description, retrying = self._TopHandleStageException()
384
385       raise
386     except Exception as e:
387       if mox is not None and isinstance(e, mox.Error):
388         raise
389
390       # Tell the build bot this step failed for the waterfall.
391       result, description, retrying = self._TopHandleStageException()
392       if result not in (results_lib.Results.FORGIVEN,
393                         results_lib.Results.SUCCESS):
394         raise failures_lib.StepFailure()
395       elif retrying:
396         raise failures_lib.RetriableStepFailure()
397     except BaseException:
398       result, description, retrying = self._TopHandleStageException()
399       raise
400     finally:
401       elapsed_time = time.time() - start_time
402       self._RecordResult(self.name, result, description, prefix=self._prefix,
403                          time=elapsed_time)
404       self._Finish()
405       sys.stdout.flush()
406       sys.stderr.flush()
407
408
409 class NonHaltingBuilderStage(BuilderStage):
410   """Build stage that fails a build but finishes the other steps."""
411
412   def Run(self):
413     try:
414       super(NonHaltingBuilderStage, self).Run()
415     except failures_lib.StepFailure:
416       name = self.__class__.__name__
417       cros_build_lib.Error('Ignoring StepFailure in %s', name)
418
419
420 class ForgivingBuilderStage(BuilderStage):
421   """Build stage that turns a build step red but not a build."""
422
423   def _HandleStageException(self, exc_info):
424     """Override and don't set status to FAIL but FORGIVEN instead."""
425     return self._HandleExceptionAsWarning(exc_info)
426
427
428 class RetryStage(object):
429   """Retry a given stage multiple times to see if it passes."""
430
431   def __init__(self, builder_run, max_retry, stage, *args, **kwargs):
432     """Create a RetryStage object.
433
434     Args:
435       builder_run: See arguments to BuilderStage.__init__()
436       max_retry: The number of times to try the given stage.
437       stage: The stage class to create.
438       *args: A list of arguments to pass to the stage constructor.
439       **kwargs: A list of keyword arguments to pass to the stage constructor.
440     """
441     self._run = builder_run
442     self.max_retry = max_retry
443     self.stage = stage
444     self.args = (builder_run,) + args
445     self.kwargs = kwargs
446     self.names = []
447     self.attempt = None
448
449   def GetStageNames(self):
450     """Get a list of the places where this stage has recorded results."""
451     return self.names[:]
452
453   def _PerformStage(self):
454     """Run the stage once, incrementing the attempt number as needed."""
455     suffix = ' (attempt %d)' % (self.attempt,)
456     stage_obj = self.stage(
457         *self.args, attempt=self.attempt, max_retry=self.max_retry,
458         suffix=suffix, **self.kwargs)
459     self.names.extend(stage_obj.GetStageNames())
460     self.attempt += 1
461     stage_obj.Run()
462
463   def Run(self):
464     """Retry the given stage multiple times to see if it passes."""
465     self.attempt = 1
466     retry_util.RetryException(
467         failures_lib.RetriableStepFailure, self.max_retry, self._PerformStage)
468
469
470 class RepeatStage(object):
471   """Run a given stage multiple times to see if it fails."""
472
473   def __init__(self, builder_run, count, stage, *args, **kwargs):
474     """Create a RepeatStage object.
475
476     Args:
477       builder_run: See arguments to BuilderStage.__init__()
478       count: The number of times to try the given stage.
479       stage: The stage class to create.
480       *args: A list of arguments to pass to the stage constructor.
481       **kwargs: A list of keyword arguments to pass to the stage constructor.
482     """
483     self._run = builder_run
484     self.count = count
485     self.stage = stage
486     self.args = (builder_run,) + args
487     self.kwargs = kwargs
488     self.names = []
489     self.attempt = None
490
491   def GetStageNames(self):
492     """Get a list of the places where this stage has recorded results."""
493     return self.names[:]
494
495   def _PerformStage(self):
496     """Run the stage once."""
497     suffix = ' (attempt %d)' % (self.attempt,)
498     stage_obj = self.stage(
499         *self.args, attempt=self.attempt, suffix=suffix, **self.kwargs)
500     self.names.extend(stage_obj.GetStageNames())
501     stage_obj.Run()
502
503   def Run(self):
504     """Retry the given stage multiple times to see if it passes."""
505     for i in range(self.count):
506       self.attempt = i + 1
507       self._PerformStage()
508
509
510 class BoardSpecificBuilderStage(BuilderStage):
511   """Builder stage that is specific to a board.
512
513   The following attributes are provided on self:
514     _current_board: The active board for this stage.
515     board_runattrs: BoardRunAttributes object for this stage.
516   """
517
518   def __init__(self, builder_run, board, **kwargs):
519     super(BoardSpecificBuilderStage, self).__init__(builder_run, **kwargs)
520     self._current_board = board
521
522     self.board_runattrs = builder_run.GetBoardRunAttrs(board)
523
524     if not isinstance(board, basestring):
525       raise TypeError('Expected string, got %r' % (board,))
526
527     # Add a board name suffix to differentiate between various boards (in case
528     # more than one board is built on a single builder.)
529     if len(self._boards) > 1 or self._run.config.grouped:
530       self.name = '%s [%s]' % (self.name, board)
531
532   def _RecordResult(self, *args, **kwargs):
533     """Record a successful or failed result."""
534     kwargs.setdefault('board', self._current_board)
535     super(BoardSpecificBuilderStage, self)._RecordResult(*args, **kwargs)
536
537   def GetParallel(self, board_attr, timeout=None, pretty_name=None):
538     """Wait for given |board_attr| to show up.
539
540     Args:
541       board_attr: A valid board runattribute name.
542       timeout: Timeout in seconds.  None value means wait forever.
543       pretty_name: Optional name to use instead of raw board_attr in
544         log messages.
545
546     Returns:
547       Value of board_attr found.
548
549     Raises:
550       AttrTimeoutError if timeout occurs.
551     """
552     timeout_str = 'forever'
553     if timeout is not None:
554       timeout_str = '%d minutes' % int((timeout / 60) + 0.5)
555
556     if pretty_name is None:
557       pretty_name = board_attr
558
559     cros_build_lib.Info('Waiting up to %s for %s ...', timeout_str, pretty_name)
560     return self.board_runattrs.GetParallel(board_attr, timeout=timeout)
561
562   def GetImageDirSymlink(self, pointer='latest-cbuildbot'):
563     """Get the location of the current image."""
564     return os.path.join(self._run.buildroot, 'src', 'build', 'images',
565                         self._current_board, pointer)
566
567
568 class ArchivingStageMixin(object):
569   """Stage with utilities for uploading artifacts.
570
571   This provides functionality for doing archiving.  All it needs is access
572   to the BuilderRun object at self._run.  No __init__ needed.
573
574   Attributes:
575     acl: GS ACL to use for uploads.
576     archive: Archive object.
577     archive_path: Local path where archives are kept for this run.  Also copy
578       of self.archive.archive_path.
579     download_url: The URL where artifacts for this run can be downloaded.
580       Also copy of self.archive.download_url.
581     upload_url: The Google Storage location where artifacts for this run should
582       be uploaded.  Also copy of self.archive.upload_url.
583     version: Copy of self.archive.version.
584   """
585
586   PROCESSES = 10
587
588   @property
589   def archive(self):
590     """Retrieve the Archive object to use."""
591     # pylint: disable=W0201
592     if not hasattr(self, '_archive'):
593       self._archive = self._run.GetArchive()
594
595     return self._archive
596
597   @property
598   def acl(self):
599     """Retrieve GS ACL to use for uploads."""
600     return self.archive.upload_acl
601
602   # TODO(mtennant): Get rid of this property.
603   @property
604   def version(self):
605     """Retrieve the ChromeOS version for the archiving."""
606     return self.archive.version
607
608   @property
609   def archive_path(self):
610     """Local path where archives are kept for this run."""
611     return self.archive.archive_path
612
613   # TODO(mtennant): Rename base_archive_path.
614   @property
615   def bot_archive_root(self):
616     """Path of directory one level up from self.archive_path."""
617     return os.path.dirname(self.archive_path)
618
619   @property
620   def upload_url(self):
621     """The GS location where artifacts should be uploaded for this run."""
622     return self.archive.upload_url
623
624   @property
625   def base_upload_url(self):
626     """The GS path one level up from self.upload_url."""
627     return os.path.dirname(self.upload_url)
628
629   @property
630   def download_url(self):
631     """The URL where artifacts for this run can be downloaded."""
632     return self.archive.download_url
633
634   @contextlib.contextmanager
635   def ArtifactUploader(self, queue=None, archive=True, strict=True):
636     """Upload each queued input in the background.
637
638     This context manager starts a set of workers in the background, who each
639     wait for input on the specified queue. These workers run
640     self.UploadArtifact(*args, archive=archive) for each input in the queue.
641
642     Args:
643       queue: Queue to use. Add artifacts to this queue, and they will be
644         uploaded in the background.  If None, one will be created on the fly.
645       archive: Whether to automatically copy files to the archive dir.
646       strict: Whether to treat upload errors as fatal.
647
648     Returns:
649       The queue to use. This is only useful if you did not supply a queue.
650     """
651     upload = lambda path: self.UploadArtifact(path, archive, strict)
652     with parallel.BackgroundTaskRunner(upload, queue=queue,
653                                        processes=self.PROCESSES) as bg_queue:
654       yield bg_queue
655
656   def PrintDownloadLink(self, filename, prefix='', text_to_display=None):
657     """Print a link to an artifact in Google Storage.
658
659     Args:
660       filename: The filename of the uploaded file.
661       prefix: The prefix to put in front of the filename.
662       text_to_display: Text to display. If None, use |prefix| + |filename|.
663     """
664     url = '%s/%s' % (self.download_url.rstrip('/'), filename)
665     if not text_to_display:
666       text_to_display = '%s%s' % (prefix, filename)
667     cros_build_lib.PrintBuildbotLink(text_to_display, url)
668
669   def _IsInUploadBlacklist(self, filename):
670     """Check if this file is blacklisted to go into a board's extra buckets.
671
672     Args:
673       filename: The filename of the file we want to check is in the blacklist.
674
675     Returns:
676       True if the file is blacklisted, False otherwise.
677     """
678     for blacklisted_file in constants.EXTRA_BUCKETS_FILES_BLACKLIST:
679       if fnmatch.fnmatch(filename, blacklisted_file):
680         return True
681     return False
682
683   def _GetUploadUrls(self, filename, builder_run=None):
684     """Returns a list of all urls for which to upload filename to.
685
686     Args:
687       filename: The filename of the file we want to upload.
688       builder_run: builder_run object from which to get the board, base upload
689                    url, and bot_id. If none, this stage's values.
690     """
691     board = None
692     urls = [self.upload_url]
693     bot_id = self._bot_id
694     if builder_run:
695       urls = [builder_run.GetArchive().upload_url]
696       bot_id = builder_run.GetArchive().bot_id
697       if (builder_run.config['boards'] and
698           len(builder_run.config['boards']) == 1):
699         board = builder_run.config['boards'][0]
700     if (not self._IsInUploadBlacklist(filename) and
701         (hasattr(self, '_current_board') or board)):
702       board = board or self._current_board
703       custom_artifacts_file = portage_util.ReadOverlayFile(
704           'scripts/artifacts.json', board=board)
705       if custom_artifacts_file is not None:
706         json_file = json.loads(custom_artifacts_file)
707         for url in json_file.get('extra_upload_urls', []):
708           urls.append('/'.join([url, bot_id, self.version]))
709     return urls
710
711   @failures_lib.SetFailureType(failures_lib.InfrastructureFailure)
712   def UploadArtifact(self, path, archive=True, strict=True):
713     """Upload generated artifact to Google Storage.
714
715     Args:
716       path: Path of local file to upload to Google Storage
717         if |archive| is True. Otherwise, this is the name of the file
718         in self.archive_path.
719       archive: Whether to automatically copy files to the archive dir.
720       strict: Whether to treat upload errors as fatal.
721     """
722     filename = path
723     if archive:
724       filename = commands.ArchiveFile(path, self.archive_path)
725     upload_urls = self._GetUploadUrls(filename)
726     try:
727       commands.UploadArchivedFile(
728           self.archive_path, upload_urls, filename, self._run.debug,
729           update_list=True, acl=self.acl)
730     except failures_lib.GSUploadFailure as e:
731       cros_build_lib.PrintBuildbotStepText('Upload failed')
732       if e.HasFatalFailure(
733           whitelist=[gs.GSContextException, timeout_util.TimeoutError]):
734         raise
735       elif strict:
736         raise
737       else:
738         # Treat gsutil flake as a warning if it's the only problem.
739         self._HandleExceptionAsWarning(sys.exc_info())
740
741   @failures_lib.SetFailureType(failures_lib.InfrastructureFailure)
742   def UploadMetadata(self, upload_queue=None, filename=None):
743     """Create and upload JSON file of the builder run's metadata, and to cidb.
744
745     This uses the existing metadata stored in the builder run. The default
746     metadata.json file should only be uploaded once, at the end of the run,
747     and considered immutable. During the build, intermediate metadata snapshots
748     can be uploaded to other files, such as partial-metadata.json.
749
750     This method also updates the metadata in the cidb database, if there is a
751     valid cidb connection set up.
752
753     Args:
754       upload_queue: If specified then put the artifact file to upload on
755         this queue.  If None then upload it directly now.
756       filename: Name of file to dump metadata to.
757                 Defaults to constants.METADATA_JSON
758     """
759     filename = filename or constants.METADATA_JSON
760
761     metadata_json = os.path.join(self.archive_path, filename)
762
763     # Stages may run in parallel, so we have to do atomic updates on this.
764     cros_build_lib.Info('Writing metadata to %s.', metadata_json)
765     osutils.WriteFile(metadata_json, self._run.attrs.metadata.GetJSON(),
766                       atomic=True, makedirs=True)
767
768     if upload_queue is not None:
769       cros_build_lib.Info('Adding metadata file %s to upload queue.',
770                           metadata_json)
771       upload_queue.put([filename])
772     else:
773       cros_build_lib.Info('Uploading metadata file %s now.', metadata_json)
774       self.UploadArtifact(filename, archive=False)
775
776     if cidb.CIDBConnectionFactory.IsCIDBSetup():
777       db = cidb.CIDBConnectionFactory.GetCIDBConnectionForBuilder()
778       if db:
779         build_id = self._run.attrs.metadata.GetValue('build_id')
780         cros_build_lib.Info('Writing updated metadata to database for build_id '
781                             '%s.', build_id)
782         db.UpdateMetadata(build_id, self._run.attrs.metadata)
783       else:
784         cros_build_lib.Info('Skipping database update.')