| Index: commit-queue/verification/try_job_on_rietveld.py
|
| ===================================================================
|
| --- commit-queue/verification/try_job_on_rietveld.py (revision 249146)
|
| +++ commit-queue/verification/try_job_on_rietveld.py (working copy)
|
| @@ -1,755 +0,0 @@
|
| -# coding=utf8
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -"""Sends patches to the Try server and reads back results.
|
| -
|
| -- RietveldTryJobs contains RietveldTryJob, one per try job on a builder.
|
| -- TryRunnerRietveld uses Rietveld to signal and poll job results.
|
| -"""
|
| -
|
| -import collections
|
| -import errno
|
| -import logging
|
| -import re
|
| -import socket
|
| -import time
|
| -import urllib2
|
| -
|
| -import buildbot_json
|
| -import model
|
| -from verification import base
|
| -from verification import try_job_steps
|
| -
|
| -# A build running for longer than this is considered to be timed out.
|
| -TIMED_OUT = 12 * 60 * 60
|
| -
|
| -
|
| -def is_job_expired(now, revision, timestamp, checkout):
|
| - """Returns False if the job result is still somewhat valid.
|
| -
|
| - A job that occured more than 4 days ago or more than 200 commits behind
|
| - is 'expired'.
|
| - """
|
| - if timestamp < (now - 4*24*60*60):
|
| - return True
|
| - if checkout.revisions(revision, None) >= 200:
|
| - return True
|
| - return False
|
| -
|
| -
|
| -TryJobProperties = collections.namedtuple(
|
| - 'TryJobProperties',
|
| - ['key', 'parent_key', 'builder', 'build', 'buildnumber', 'properties'])
|
| -
|
| -
|
| -def filter_jobs(try_job_results, watched_builders, current_irrelevant_keys,
|
| - status):
|
| - """For each try jobs results, query the Try Server for updated status and
|
| - returns details about each job in a TryJobProperties.
|
| -
|
| - Returns a list of namedtuple describing the updated results and and the new
|
| - list of irrelevant keys.
|
| -
|
| - It adds the build to the ignored list if the build doesn't exist on the Try
|
| - Server anymore (usually it's too old) or if the try job was not triggered by
|
| - the Commit Queue itself.
|
| - """
|
| - irrelevant = set(current_irrelevant_keys)
|
| - try_jobs_with_props = []
|
| - for result in try_job_results:
|
| - key = result['key']
|
| - assert key
|
| - if key in current_irrelevant_keys:
|
| - continue
|
| - builder = result['builder']
|
| - try:
|
| - buildnumber = int(result['buildnumber'])
|
| - except (TypeError, ValueError):
|
| - continue
|
| - if buildnumber < 0:
|
| - logging.debug('Ignoring %s/%d; invalid', builder, buildnumber)
|
| - irrelevant.add(key)
|
| - continue
|
| -
|
| - if builder not in watched_builders:
|
| - logging.debug('Ignoring %s/%d; no step verifier is examining it', builder,
|
| - buildnumber)
|
| - irrelevant.add(key)
|
| - continue
|
| -
|
| - # Constructing the object itself doesn't throw an exception, it's reading
|
| - # its properties that throws.
|
| - build = status.builders[builder].builds[buildnumber]
|
| - try:
|
| - props = build.properties_as_dict
|
| - except IOError:
|
| - logging.info(
|
| - 'Build %s/%s is not on the try server anymore',
|
| - builder, buildnumber)
|
| - irrelevant.add(key)
|
| - continue
|
| - parent_key = props.get('parent_try_job_key')
|
| - if parent_key:
|
| - # Triggered build
|
| - key = '%s/%d_triggered_%s' % (builder, buildnumber, parent_key)
|
| - elif props.get('try_job_key') != key:
|
| - # not triggered, not valid
|
| - logging.debug(
|
| - 'Ignoring %s/%d; not from rietveld', builder, buildnumber)
|
| - irrelevant.add(key)
|
| - continue
|
| -
|
| - try_jobs_with_props.append(
|
| - TryJobProperties(key, parent_key, builder, build, buildnumber, props))
|
| -
|
| - # Sort the non-triggered builds first so triggered jobs
|
| - # can expect their parent to be added to self.try_jobs
|
| - try_jobs_with_props.sort(key=lambda tup: tup.parent_key)
|
| -
|
| - return try_jobs_with_props, list(irrelevant)
|
| -
|
| -
|
| -def _is_skip_try_job(pending):
|
| - """Returns True if a description contains NOTRY=true."""
|
| - match = re.search(r'^NOTRY=(.*)$', pending.description, re.MULTILINE)
|
| - return match and match.group(1).lower() == 'true'
|
| -
|
| -
|
| -class RietveldTryJobPending(model.PersistentMixIn):
|
| - """Represents a pending try job for a pending commit that we care about.
|
| -
|
| - It is immutable.
|
| - """
|
| - builder = unicode
|
| - revision = (None, unicode, int)
|
| - requested_steps = list
|
| - clobber = bool
|
| - # Number of retries for this configuration. Initial try is 1.
|
| - tries = int
|
| - init_time = float
|
| -
|
| - def __init__(self, **kwargs):
|
| - required = set(self._persistent_members())
|
| - actual = set(kwargs)
|
| - assert required == actual, (required - actual, required, actual)
|
| - super(RietveldTryJobPending, self).__init__(**kwargs)
|
| - # Then mark it read-only.
|
| - self._read_only = True
|
| -
|
| -
|
| -class RietveldTryJob(model.PersistentMixIn):
|
| - """Represents a try job for a pending commit that we care about.
|
| -
|
| - This data can be regenerated by parsing all the try job names but it is a bit
|
| - hard on the try server.
|
| -
|
| - It is immutable.
|
| - """
|
| - builder = unicode
|
| - build = int
|
| - revision = (None, unicode, int)
|
| - requested_steps = list
|
| - # The timestamp when the build started. buildbot_json returns int.
|
| - started = int
|
| - steps_passed = list
|
| - steps_failed = list
|
| - clobber = bool
|
| - completed = bool
|
| - # Number of retries for this configuration. Initial try is 1.
|
| - tries = int
|
| - parent_key = (None, unicode)
|
| - init_time = float
|
| -
|
| - def __init__(self, **kwargs):
|
| - required = set(self._persistent_members())
|
| - actual = set(kwargs)
|
| - assert required == actual, (required - actual, required, actual)
|
| - super(RietveldTryJob, self).__init__(**kwargs)
|
| - # Then mark it read-only.
|
| - self._read_only = True
|
| -
|
| - @property
|
| - @model.immutable
|
| - def result(self):
|
| - if self.steps_failed:
|
| - return buildbot_json.FAILURE
|
| - if self.completed:
|
| - return buildbot_json.SUCCESS
|
| - return None
|
| -
|
| -
|
| -class RietveldTryJobs(base.IVerifierStatus):
|
| - """A set of try jobs that were sent for a specific patch.
|
| -
|
| - Multiple concurrent try jobs can be sent on a single builder. For example, a
|
| - previous valid try job could have been triggered by the user but was not
|
| - completed so another was sent with the missing tests.
|
| - Also, a try job is sent as soon as a test failure is detected.
|
| - """
|
| - # An dict of RietveldTryJob objects per key.
|
| - try_jobs = dict
|
| - # The try job keys we ignore because they can't be used to give a good
|
| - # signal: either they are too old (old revision) or they were not triggerd
|
| - # by Rietveld, so we don't know if the diff is 100% good.
|
| - irrelevant = list
|
| - # When NOTRY=true is specified.
|
| - skipped = bool
|
| - # List of test verifiers. All the logic to decide when they are
|
| - # and what bots they trigger is hidden inside.
|
| - step_verifiers = list
|
| - # Jobs that have been sent but are not found yet. Likely a builder is fully
|
| - # utilized or the try server hasn't polled Rietveld yet. list of
|
| - # RietveldTryJobPending() instances.
|
| - pendings = list
|
| -
|
| - @model.immutable
|
| - def get_state(self):
|
| - """Returns the state of this verified.
|
| -
|
| - Failure can be from:
|
| - - For each entry in self.step_verifiers:
|
| - - A Try Job in self.try_jobs has been retried too often.
|
| -
|
| - In particular, there is no need to wait for every Try Job to complete.
|
| - """
|
| - if self.error_message:
|
| - return base.FAILED
|
| - if not self.tests_waiting_for_result():
|
| - return base.SUCCEEDED
|
| - return base.PROCESSING
|
| -
|
| - @model.immutable
|
| - def tests_need_to_be_run(self, now):
|
| - """Returns which tests need to be run.
|
| -
|
| - These are the tests that are not pending on any try job, either running or
|
| - in the pending list.
|
| - """
|
| - # Skipped or failed, nothing to do.
|
| - if self.skipped or self.error_message:
|
| - return {}
|
| -
|
| - # What originally needed to be run.
|
| - # All_tests is {builder_name: set(test_name*)}
|
| - all_tests = {}
|
| - for verifier in self.step_verifiers:
|
| - (builder, tests) = verifier.need_to_trigger(self.try_jobs, now)
|
| - if tests:
|
| - all_tests.setdefault(builder, set()).update(tests)
|
| -
|
| - # Removes what is queued to be run but hasn't started yet.
|
| - for try_job in self.pendings:
|
| - if try_job.builder in all_tests:
|
| - all_tests[try_job.builder] -= set(try_job.requested_steps)
|
| -
|
| - return dict(
|
| - (builder, sorted(tests)) for builder, tests in all_tests.iteritems()
|
| - if tests)
|
| -
|
| - @model.immutable
|
| - def tests_waiting_for_result(self):
|
| - """Returns the tests that we are waiting for results on pending or running
|
| - builds.
|
| - """
|
| - # Skipped or failed, nothing to do.
|
| - if self.skipped or self.error_message:
|
| - return {}
|
| -
|
| - # What originally needed to be run.
|
| - all_tests = {}
|
| - for verification in self.step_verifiers:
|
| - (builder, tests) = verification.waiting_for(self.try_jobs)
|
| - if tests:
|
| - all_tests.setdefault(builder, set()).update(tests)
|
| -
|
| - # Removes what was run.
|
| - for try_job in self.try_jobs.itervalues():
|
| - if try_job.builder in all_tests:
|
| - all_tests[try_job.builder] -= set(try_job.steps_passed)
|
| -
|
| - return dict(
|
| - (builder, list(tests)) for builder, tests in all_tests.iteritems()
|
| - if tests)
|
| -
|
| - @model.immutable
|
| - def watched_builders(self):
|
| - """Marks all the jobs that the step_verifiers don't examine as
|
| - irrelevant.
|
| - """
|
| - # Generate the list of builders to keep.
|
| - watched_builders = set()
|
| - for step_verifier in self.step_verifiers:
|
| - watched_builders.add(step_verifier.builder_name)
|
| - if isinstance(step_verifier, try_job_steps.TryJobTriggeredSteps):
|
| - watched_builders.add(step_verifier.trigger_name)
|
| -
|
| - return watched_builders
|
| -
|
| - def update_jobs_from_rietveld(
|
| - self, data, status, checkout, now):
|
| - """Retrieves the jobs statuses from rietveld and updates its state.
|
| -
|
| - Args:
|
| - owner: Owner of the CL.
|
| - data: Patchset properties as returned from Rietveld.
|
| - status: A buildbot_json.Buildbot instance.
|
| - checkout: A depot_tools' Checkout instance.
|
| - now: epoch time of what should be considered to be 'now'.
|
| -
|
| - Returns:
|
| - Keys which were updated.
|
| - """
|
| - updated = []
|
| - try_job_results = data.get('try_job_results', [])
|
| - logging.debug('Found %d entries', len(try_job_results))
|
| -
|
| - try_jobs_with_props, self.irrelevant = filter_jobs(
|
| - try_job_results, self.watched_builders() , self.irrelevant, status)
|
| -
|
| - # Ensure that all irrelevant jobs have been removed from the set of valid
|
| - # try jobs.
|
| - for irrelevant_key in self.irrelevant:
|
| - if irrelevant_key in self.try_jobs:
|
| - del self.try_jobs[irrelevant_key]
|
| - if irrelevant_key + '_old' in self.try_jobs:
|
| - del self.try_jobs[irrelevant_key + '_old']
|
| -
|
| - for i in try_jobs_with_props:
|
| - if self._update_try_job_status(checkout, i, now):
|
| - updated.append(i.key)
|
| - return updated
|
| -
|
| - def _update_try_job_status(self, checkout, try_job_properties, now):
|
| - """Updates status of a specific RietveldTryJob.
|
| -
|
| - try_job_property is an instance of TryJobProperties.
|
| -
|
| - Returns True if it was updated.
|
| - """
|
| - key = try_job_properties.key
|
| - builder = try_job_properties.builder
|
| - buildnumber = try_job_properties.buildnumber
|
| - if key in self.irrelevant:
|
| - logging.debug('Ignoring %s/%d; irrelevant', builder, buildnumber)
|
| - return False
|
| - if (try_job_properties.parent_key and
|
| - try_job_properties.parent_key not in self.try_jobs):
|
| - logging.debug('Ignoring %s, parent unknown', key)
|
| - return False
|
| -
|
| - requested_steps = []
|
| - # Set it to 0 as the default value since when the job is new and previous
|
| - # try jobs are found, we don't want to count them as tries.
|
| - tries = 0
|
| - job = self.try_jobs.get(key)
|
| - build = try_job_properties.build
|
| - if job:
|
| - if job.completed:
|
| - logging.debug('Ignoring %s/%d; completed', builder, buildnumber)
|
| - return False
|
| - else:
|
| - if now - job.started > TIMED_OUT:
|
| - # Flush it and start over.
|
| - self.irrelevant.append(key)
|
| - del self.try_jobs[key]
|
| - return False
|
| - requested_steps = job.requested_steps
|
| - tries = job.tries
|
| - init_time = job.init_time
|
| - else:
|
| - # This try job is new. See if we triggered it previously by
|
| - # looking in self.pendings.
|
| - for index, pending_job in enumerate(self.pendings):
|
| - if pending_job.builder == builder:
|
| - # Reuse its item.
|
| - requested_steps = pending_job.requested_steps
|
| - tries = pending_job.tries
|
| - self.pendings.pop(index)
|
| - break
|
| - else:
|
| - # Is this a good build? It must not be too old and triggered by
|
| - # rietveld.
|
| - if is_job_expired(now, build.revision, build.start_time, checkout):
|
| - logging.debug('Ignoring %s/%d; expired', builder, buildnumber)
|
| - self.irrelevant.append(key)
|
| - return False
|
| - init_time = now
|
| -
|
| - passed = [s.name for s in build.steps if s.simplified_result]
|
| - failed = [s.name for s in build.steps if s.simplified_result is False]
|
| - # The steps in neither passed or failed were skipped.
|
| - new_job = RietveldTryJob(
|
| - init_time=init_time,
|
| - builder=builder,
|
| - build=buildnumber,
|
| - revision=build.revision,
|
| - requested_steps=requested_steps,
|
| - started=build.start_time,
|
| - steps_passed=passed,
|
| - steps_failed=failed,
|
| - clobber=bool(try_job_properties.properties.get('clobber')),
|
| - completed=build.completed,
|
| - tries=tries,
|
| - parent_key=try_job_properties.parent_key)
|
| - if job and job.build and new_job.build and job.build != new_job.build:
|
| - # It's tricky because 'key' is the same for both. The trick is to create
|
| - # a fake key for the old build and mark it as completed. Note that
|
| - # Rietveld is confused by it too.
|
| - logging.warning(
|
| - 'Try Server was restarted and restarted builds with the same keys. '
|
| - 'I\'m confused. %s: %d != %d', job.builder, job.build, new_job.build)
|
| - # Resave the old try job and mark it as completed.
|
| - self.try_jobs[key + '_old'] = RietveldTryJob(
|
| - init_time=job.init_time,
|
| - builder=job.builder,
|
| - build=job.build,
|
| - revision=job.revision,
|
| - requested_steps=job.requested_steps,
|
| - started=build.start_time,
|
| - steps_passed=job.steps_passed,
|
| - steps_failed=job.steps_failed,
|
| - clobber=job.clobber,
|
| - completed=True,
|
| - tries=job.tries,
|
| - parent_key=job.parent_key)
|
| - if not job or not model.is_equivalent(new_job, job):
|
| - logging.info(
|
| - 'Job update: %s: %s/%d',
|
| - try_job_properties.properties.get('issue'),
|
| - builder,
|
| - buildnumber)
|
| - self.try_jobs[key] = new_job
|
| - return key
|
| -
|
| - def signal_as_failed_if_needed(self, job, url, now):
|
| - """Detects if the RietveldTryJob instance is in a state where it is
|
| - impossible to make progress.
|
| -
|
| - If so, mark ourself as failed by setting self.error_message and return True.
|
| - """
|
| - if self.skipped or self.error_message:
|
| - return False
|
| - # Figure out steps that should be retried for this builder.
|
| - missing_tests = self.tests_need_to_be_run(now).get(job.builder, [])
|
| - if not missing_tests:
|
| - return False
|
| - if job.tries > 2:
|
| - self.error_message = (
|
| - 'Retried try job too often on %s for step(s) %s\n%s' %
|
| - (job.builder, ', '.join(missing_tests), url))
|
| - logging.info(self.error_message)
|
| - return True
|
| - return False
|
| -
|
| - @model.immutable
|
| - def why_not(self):
|
| - # Skipped or failed, nothing to do.
|
| - if self.skipped or self.error_message:
|
| - return None
|
| - waiting = self.tests_waiting_for_result()
|
| - if waiting:
|
| - out = 'Waiting for the following jobs:\n'
|
| - for builder in sorted(waiting):
|
| - out += ' %s: %s\n' % (builder, ','.join(waiting[builder]))
|
| - return out
|
| -
|
| -
|
| -class TryRunnerRietveld(base.VerifierCheckout):
|
| - """Stateless communication with a try server.
|
| -
|
| - Uses Rietveld to trigger the try job and reads try job status with the json
|
| - API.
|
| -
|
| - Analysis goes as following:
|
| - - compile step itself is not flaky. compile.py already takes care of most
|
| - flakiness and clobber build is done by default. If compile step fails, try
|
| - again with clobber=True
|
| - - test steps are flaky and can be retried as necessary.
|
| -
|
| - 1. For each existing try jobs from rietveld.
|
| - 1. Fetch result from try server.
|
| - 2. If try job was generated from rietveld;
|
| - 1. If not is_job_expired();
|
| - 1. Skip any scheduled test that succeeded on this builder.
|
| - 2. For each builder with tests scheduled;
|
| - 1. If no step waiting to be triggered, skip this builder completely.
|
| - 2. For each non succeeded job;
|
| - 1. Send try jobs to rietveld.
|
| -
|
| - Note: It needs rietveld, hence it uses VerifierCheckout, but it doesn't need a
|
| - checkout.
|
| - """
|
| - name = 'try job rietveld'
|
| -
|
| - # Only updates a job status once every 60 seconds.
|
| - update_latency = 60
|
| -
|
| - def __init__(
|
| - self, context_obj, try_server_url, commit_user, step_verifiers,
|
| - ignored_steps, solution):
|
| - super(TryRunnerRietveld, self).__init__(context_obj)
|
| - self.try_server_url = try_server_url.rstrip('/')
|
| - self.commit_user = commit_user
|
| - # TODO(maruel): Have it be overridden by presubmit_support.DoGetTrySlaves.
|
| - self.step_verifiers = step_verifiers
|
| - self.ignored_steps = set(ignored_steps)
|
| - # Time to poll the Try Server, and not Rietveld.
|
| - self.last_update = time.time() - self.update_latency
|
| - self.solution = solution
|
| -
|
| - def verify(self, pending):
|
| - """Sends a try job to the try server and returns a RietveldTryJob list.
|
| -
|
| - This function is called synchronously.
|
| - """
|
| - jobs = pending.verifications.setdefault(self.name, RietveldTryJobs())
|
| - if _is_skip_try_job(pending):
|
| - # Do not run try job for it.
|
| - jobs.skipped = True
|
| - return
|
| -
|
| - # Overridde any previous list from the last restart.
|
| - jobs.step_verifiers = []
|
| - for step in self.step_verifiers:
|
| - if isinstance(step, try_job_steps.TryJobTriggeredOrNormalSteps):
|
| - # Since the steps are immutable, create a new step so that swarm
|
| - # can be enabled.
|
| - jobs.step_verifiers.append(try_job_steps.TryJobTriggeredOrNormalSteps(
|
| - builder_name=step.builder_name,
|
| - trigger_name=step.trigger_name,
|
| - steps=step.steps,
|
| - trigger_bot_steps=step.trigger_bot_steps,
|
| - use_triggered_bot=True))
|
| - else:
|
| - jobs.step_verifiers.append(step)
|
| -
|
| - # First, update the status of the current try jobs on Rietveld.
|
| - now = time.time()
|
| - self._update_jobs_from_rietveld(pending, jobs, False, now)
|
| -
|
| - # Add anything that is missing.
|
| - self._send_jobs(pending, jobs, now)
|
| -
|
| - # Slightly postpone next check.
|
| - self.last_update = min(now, self.last_update + (self.update_latency / 4))
|
| -
|
| - def update_status(self, queue):
|
| - """Grabs the current status of all try jobs and update self.queue.
|
| -
|
| - Note: it would be more efficient to be event based.
|
| - """
|
| - if not queue:
|
| - logging.debug('The list is empty, nothing to do')
|
| - return
|
| -
|
| - # Hard code 'now' to the value before querying and sending them. This will
|
| - # cause some issues when querying state or sending the jobs takes a
|
| - # non-trivial amount of time but in general it will be fine.
|
| - now = time.time()
|
| - if now - self.last_update < self.update_latency:
|
| - logging.debug('TS: Throttling updates')
|
| - return
|
| - self.last_update = now
|
| -
|
| - # Update the status of the current pending CLs on Rietveld.
|
| - for pending, jobs in self.loop(queue, RietveldTryJobs, True):
|
| - # Update 'now' since querying the try jobs may take a significant amount
|
| - # of time.
|
| - now = time.time()
|
| - if self._update_jobs_from_rietveld(pending, jobs, True, now):
|
| - # Send any necessary job. Noop if not needed.
|
| - self._send_jobs(pending, jobs, now)
|
| -
|
| - def _add_pending_job_and_send_if_needed(self, builder, steps, jobs,
|
| - send_job, pending, now):
|
| - # Find if there was a previous try.
|
| - previous_jobs = [
|
| - job for job in jobs.try_jobs.itervalues() if job.builder == builder
|
| - ]
|
| - if previous_jobs:
|
| - tries = max(job.tries for job in previous_jobs)
|
| - clobber = max(
|
| - (job.clobber or 'compile' in job.steps_failed)
|
| - for job in previous_jobs)
|
| - else:
|
| - tries = 0
|
| - clobber = False
|
| - if tries > 4:
|
| - # Fail safe.
|
| - jobs.error_message = (
|
| - ( 'The commit queue went berserk retrying too often for a\n'
|
| - 'seemingly flaky test on builder %s:\n%s') %
|
| - ( builder,
|
| - '\n'.join(self._build_status_url(j) for j in previous_jobs)))
|
| - return False
|
| -
|
| - # Don't always send the job (triggered bots don't need to send there own
|
| - # request).
|
| - if send_job:
|
| - logging.debug(
|
| - 'Sending job %s for %s: %s', pending.issue, builder, ','.join(steps))
|
| - try:
|
| - self.context.rietveld.trigger_try_jobs(
|
| - pending.issue, pending.patchset, 'CQ', clobber, 'HEAD',
|
| - {builder: steps})
|
| - except urllib2.HTTPError as e:
|
| - if e.code == 400:
|
| - # This probably mean a new patchset was uploaded since the last poll,
|
| - # so it's better to drop the CL.
|
| - jobs.error_message = 'Failed to trigger a try job on %s\n%s' % (
|
| - builder, e)
|
| - return False
|
| - else:
|
| - raise
|
| -
|
| - # Set the status of this pending job here and on the CQ page.
|
| - jobs.pendings.append(
|
| - RietveldTryJobPending(
|
| - init_time=now,
|
| - builder=builder,
|
| - revision=None,
|
| - requested_steps=steps,
|
| - clobber=clobber,
|
| - tries=tries + 1))
|
| - # Update the status on the AppEngine status to signal a new try job was
|
| - # sent.
|
| - info = {
|
| - 'builder': builder,
|
| - 'clobber': clobber,
|
| - 'job_name': 'CQ',
|
| - 'revision': None, #revision,
|
| - }
|
| - self.send_status(pending, info)
|
| - return True
|
| -
|
| - def _get_triggered_bots(self, builder, steps):
|
| - """Returns a dict of all the (builder, steps) pairs of bots that will get
|
| - triggered by the given builder steps combination."""
|
| - triggered_bots = {}
|
| - for verifier in self.step_verifiers:
|
| - builder, steps = verifier.get_triggered_steps(builder, steps)
|
| - if steps:
|
| - triggered_bots[builder] = steps
|
| -
|
| - return triggered_bots
|
| -
|
| - def _send_jobs(self, pending, jobs, now):
|
| - """Prepares the RietveldTryJobs instance |jobs| to send try jobs to the try
|
| - server.
|
| - """
|
| - if jobs.error_message:
|
| - # Too late.
|
| - return
|
| - remaining = jobs.tests_need_to_be_run(now)
|
| - if not remaining:
|
| - return
|
| - # Send them in order to simplify testing.
|
| - for builder in sorted(remaining):
|
| - tests = remaining[builder]
|
| - if not self._add_pending_job_and_send_if_needed(builder, tests, jobs,
|
| - True, pending, now):
|
| - # If the main job wasn't sent, we can't skip the triggered jobs since
|
| - # they won't get triggered.
|
| - continue
|
| -
|
| - # Add any pending bots that will be triggered from this build.
|
| - triggered_bots = self._get_triggered_bots(builder, tests)
|
| - for builder, steps in triggered_bots.iteritems():
|
| - self._add_pending_job_and_send_if_needed(builder, steps, jobs, False,
|
| - pending, now)
|
| -
|
| - @model.immutable
|
| - def _build_status_url(self, job):
|
| - """Html url for this try job."""
|
| - assert job.build is not None, str(job)
|
| - return '%s/buildstatus?builder=%s&number=%s' % (
|
| - self.try_server_url, job.builder, job.build)
|
| -
|
| - @model.immutable
|
| - def _update_dashboard(self, pending, job):
|
| - """Updates the CQ dashboard with the current Try Job state as known to the
|
| - CQ.
|
| - """
|
| - logging.debug('_update_dashboard(%s/%s)', job.builder, job.build)
|
| - info = {
|
| - 'build': job.build,
|
| - 'builder': job.builder,
|
| - 'job_name': 'CQ',
|
| - 'result': job.result,
|
| - 'revision': job.revision,
|
| - 'url': self._build_status_url(job),
|
| - }
|
| - self.send_status(pending, info)
|
| -
|
| - def _update_jobs_from_rietveld(self, pending, jobs, handle, now):
|
| - """Grabs data from Rietveld and pass it to
|
| - RietveldTryJobs.update_jobs_from_rietveld().
|
| -
|
| - Returns True on success.
|
| - """
|
| - status = buildbot_json.Buildbot(self.try_server_url)
|
| - try:
|
| - try:
|
| - data = self.context.rietveld.get_patchset_properties(
|
| - pending.issue, pending.patchset)
|
| - except urllib2.HTTPError as e:
|
| - if e.code == 404:
|
| - # TODO(phajdan.jr): Maybe generate a random id to correlate the user's
|
| - # error message and exception in the logs.
|
| - # Don't put exception traceback in the user-visible message to avoid
|
| - # leaking sensitive CQ data (passwords etc).
|
| - jobs.error_message = ('Failed to get patchset properties (patchset '
|
| - 'not found?)')
|
| - logging.error(str(e))
|
| - return False
|
| - else:
|
| - raise
|
| -
|
| - # Update the RietvedTryJobs object.
|
| - keys = jobs.update_jobs_from_rietveld(
|
| - data,
|
| - status,
|
| - self.context.checkout,
|
| - now)
|
| - except urllib2.HTTPError as e:
|
| - if e.code in (500, 502, 503):
|
| - # Temporary AppEngine hiccup. Just log it and return failure.
|
| - logging.warning('%s while accessing %s. Ignoring error.' % (
|
| - str(e), e.url))
|
| - return False
|
| - else:
|
| - raise
|
| - except urllib2.URLError as e:
|
| - if 'timed out' in e.reason:
|
| - # Handle timeouts gracefully.
|
| - logging.warning('%s while updating tryserver status for '
|
| - 'rietveld issue %s', e, pending.issue)
|
| - return False
|
| - else:
|
| - raise
|
| - except socket.error as e:
|
| - # Temporary AppEngine hiccup. Just log it and return failure.
|
| - if e.errno == errno.ECONNRESET:
|
| - logging.warning(
|
| - '%s while updating tryserver status for rietveld issue %s.' % (
|
| - str(e), str(pending.issue)))
|
| - return False
|
| - else:
|
| - raise
|
| - except IOError as e:
|
| - # Temporary AppEngine hiccup. Just log it and return failure.
|
| - if e.errno == 'socket error':
|
| - logging.warning(
|
| - '%s while updating tryserver status for rietveld issue %s.' % (
|
| - str(e), str(pending.issue)))
|
| - return False
|
| - raise
|
| -
|
| - if handle:
|
| - for updated_key in keys:
|
| - job = jobs.try_jobs[updated_key]
|
| - self._update_dashboard(pending, job)
|
| - jobs.signal_as_failed_if_needed(job, self._build_status_url(job), now)
|
| -
|
| - return True
|
|
|