blob: 86fbf423637d4d20cd9dce77ff8137d4db29d7e0 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module containing the scheduler stages."""
from __future__ import print_function
import os
import time
from chromite.cbuildbot.stages import generic_stages
from chromite.lib import buildbucket_lib
from chromite.lib import build_requests
from chromite.lib import constants
from chromite.lib import config_lib
from chromite.lib import cros_logging as logging
from chromite.lib import failures_lib
from chromite.lib import request_build
from chromite.lib.const import waterfall
def BuilderName(build_config, active_waterfall, current_builder):
"""Gets the corresponding builder name of the build.
Args:
build_config: build config (string) of the build.
active_waterfall: active waterfall to run the build.
current_builder: buildbot builder name of the current builder, or None.
Returns:
Builder name to run the build on.
"""
# The builder name is configured differently for release builds in
# chromeos and chromeos_release waterfalls. (see crbug.com/755276)
if active_waterfall == waterfall.WATERFALL_RELEASE:
assert current_builder
# Example: master-release release-R64-10176.B
named_branch = current_builder.split()[1]
return '%s %s' % (build_config, named_branch)
else:
return build_config
class ScheduleSlavesStage(generic_stages.BuilderStage):
"""Stage that schedules slaves for the master build."""
category = constants.CI_INFRA_STAGE
def __init__(self, builder_run, sync_stage, **kwargs):
super(ScheduleSlavesStage, self).__init__(builder_run, **kwargs)
self.sync_stage = sync_stage
self.buildbucket_client = self.GetBuildbucketClient()
def _GetBuildbucketBucket(self, build_name, build_config):
"""Get the corresponding Buildbucket bucket.
Args:
build_name: name of the build to put to Buildbucket.
build_config: config of the build to put to Buildbucket.
Raises:
NoBuildbucketBucketFoundException when no Buildbucket bucket found.
"""
bucket = buildbucket_lib.WATERFALL_BUCKET_MAP.get(
build_config.active_waterfall)
if bucket is None:
raise buildbucket_lib.NoBuildbucketBucketFoundException(
'No Buildbucket bucket found for builder %s waterfall: %s' %
(build_name, build_config.active_waterfall))
return bucket
def PostSlaveBuildToBuildbucket(self, build_name, build_config,
master_build_id, master_buildbucket_id,
dryrun=False):
"""Send a Put slave build request to Buildbucket.
Args:
build_name: Salve build name to put to Buildbucket.
build_config: Slave build config to put to Buildbucket.
master_build_id: CIDB id of the master scheduling the slave build.
master_buildbucket_id: buildbucket id of the master scheduling the
slave build.
dryrun: Whether a dryrun, default to False.
Returns:
Tuple:
buildbucket_id
created_ts
"""
bucket = self._GetBuildbucketBucket(build_name, build_config)
luci_builder = None
if bucket != constants.INTERNAL_SWARMING_BUILDBUCKET_BUCKET:
# If it's not a swarming build, we must explicitly set this to the
# waterfall column name.
current_buildername = os.environ.get('BUILDBOT_BUILDERNAME', None)
luci_builder = BuilderName(
build_name, build_config.active_waterfall, current_buildername)
request = request_build.RequestBuild(
build_config=build_name,
luci_builder=luci_builder,
display_label=build_config.display_label,
branch=self._run.manifest_branch,
master_cidb_id=master_build_id,
master_buildbucket_id=master_buildbucket_id,
bucket=bucket,
extra_args=['--buildbot']) # TODO: This shouldn't be hard coded here.
result = request.Submit(dryrun=dryrun)
logging.info('Build_name %s buildbucket_id %s created_timestamp %s',
result.build_config, result.buildbucket_id, result.created_ts)
logging.PrintBuildbotLink(result.build_config, result.url)
return (result.buildbucket_id, result.created_ts)
def ScheduleSlaveBuildsViaBuildbucket(self, important_only=False,
dryrun=False):
"""Schedule slave builds by sending PUT requests to Buildbucket.
Args:
important_only: Whether only schedule important slave builds, default to
False.
dryrun: Whether a dryrun, default to False.
"""
if self.buildbucket_client is None:
logging.info('No buildbucket_client. Skip scheduling slaves.')
return
build_id, db = self._run.GetCIDBHandle()
if build_id is None:
logging.info('No build id. Skip scheduling slaves.')
return
# May be None. This is okay.
master_buildbucket_id = self._run.options.buildbucket_id
scheduled_important_slave_builds = []
scheduled_experimental_slave_builds = []
unscheduled_slave_builds = []
scheduled_build_reqs = []
# Get all active slave build configs.
slave_config_map = self._GetSlaveConfigMap(important_only)
for slave_config_name, slave_config in sorted(slave_config_map.iteritems()):
try:
buildbucket_id, created_ts = self.PostSlaveBuildToBuildbucket(
slave_config_name, slave_config, build_id, master_buildbucket_id,
dryrun=dryrun)
request_reason = None
if slave_config.important:
scheduled_important_slave_builds.append(
(slave_config_name, buildbucket_id, created_ts))
request_reason = build_requests.REASON_IMPORTANT_CQ_SLAVE
else:
scheduled_experimental_slave_builds.append(
(slave_config_name, buildbucket_id, created_ts))
request_reason = build_requests.REASON_EXPERIMENTAL_CQ_SLAVE
scheduled_build_reqs.append(build_requests.BuildRequest(
None, build_id, slave_config_name, None, buildbucket_id,
request_reason, None))
except buildbucket_lib.BuildbucketResponseException as e:
# Use 16-digit ts to be consistent with the created_ts from Buildbucket
current_ts = int(round(time.time() * 1000000))
unscheduled_slave_builds.append((slave_config_name, None, current_ts))
if important_only or slave_config.important:
raise
else:
logging.warning('Failed to schedule %s current timestamp %s: %s'
% (slave_config_name, current_ts, e))
if config_lib.IsMasterCQ(self._run.config) and db and scheduled_build_reqs:
db.InsertBuildRequests(scheduled_build_reqs)
self._run.attrs.metadata.ExtendKeyListWithList(
constants.METADATA_SCHEDULED_IMPORTANT_SLAVES,
scheduled_important_slave_builds)
self._run.attrs.metadata.ExtendKeyListWithList(
constants.METADATA_SCHEDULED_EXPERIMENTAL_SLAVES,
scheduled_experimental_slave_builds)
self._run.attrs.metadata.ExtendKeyListWithList(
constants.METADATA_UNSCHEDULED_SLAVES, unscheduled_slave_builds)
@failures_lib.SetFailureType(failures_lib.InfrastructureFailure)
def PerformStage(self):
if (config_lib.IsMasterCQ(self._run.config) and
not self.sync_stage.pool.HasPickedUpCLs()):
logging.info('No new CLs or chumpped CLs found to verify in this CQ run,'
'do not schedule CQ slaves.')
return
self.ScheduleSlaveBuildsViaBuildbucket(important_only=False,
dryrun=self._run.options.debug)