def cancel(self, build_id): """Cancels build. Does not require a lease key. The current user has to have a permission to cancel a build in the bucket. Returns: Canceled Build. """ build = model.Build.get_by_id(build_id) if build is None: raise errors.BuildNotFoundError() if not acl.can_cancel_build(build): raise current_identity_cannot('cancel build %s', build.key.id()) if build.status == model.BuildStatus.COMPLETED: if build.result == model.BuildResult.CANCELED: return build raise errors.BuildIsCompletedError( 'Cannot cancel a completed build') build.status = model.BuildStatus.COMPLETED build.status_changed_time = utils.utcnow() build.result = model.BuildResult.CANCELED build.cancelation_reason = model.CancelationReason.CANCELED_EXPLICITLY self._clear_lease(build) build.put() logging.info('Build %s was cancelled by %s', build.key.id(), auth.get_current_identity().to_bytes()) return build
def txn(): build = _get_leasable_build(build_id) if build.is_ended: if (build.proto.status == status and build.result_details == result_details and build.url == url): return False, build raise errors.BuildIsCompletedError( 'Build %s has already completed' % build_id) _check_lease(build, lease_key) now = utils.utcnow() build.proto.status = status build.status_changed_time = now build.proto.end_time.FromDatetime(now) if url is not None: # pragma: no branch build.url = url build.result_details = result_details if new_tags: build.tags.extend(new_tags) build.tags = sorted(set(build.tags)) build.clear_lease() _fut_results( build.put_async(), events.on_build_completing_async(build), _put_output_properties_async(build.key, result_details), ) return True, build
def txn(): build = _get_leasable_build(build_id) if build.proto.status == common_pb2.STARTED: if build.url == url: return False, build build.url = url build.put() return True, build if build.is_ended: raise errors.BuildIsCompletedError( 'Cannot start a completed build') assert build.proto.status == common_pb2.SCHEDULED _check_lease(build, lease_key) now = utils.utcnow() build.proto.start_time.FromDatetime(now) build.proto.status = common_pb2.STARTED build.status_changed_time = now build.url = url _fut_results(build.put_async(), events.on_build_starting_async(build)) return True, build
def heartbeat_async(self, build_id, lease_key, lease_expiration_date): """Extends build lease. Args: build_id: id of the build. lease_key: current lease key. lease_expiration_date (datetime.timedelta): new lease expiration date. Returns: The updated Build as Future. """ try: validate_lease_key(lease_key) if lease_expiration_date is None: raise errors.InvalidInputError( 'Lease expiration date not specified') validate_lease_expiration_date(lease_expiration_date) build = yield model.Build.get_by_id_async(build_id) if build is None: raise errors.BuildNotFoundError() if build.status == model.BuildStatus.COMPLETED: raise errors.BuildIsCompletedError() self._check_lease(build, lease_key) build.lease_expiration_date = lease_expiration_date yield build.put_async() except Exception as ex: logging.warning('Heartbeat for build %s failed: %s', build_id, ex) raise raise ndb.Return(build)
def start(self, build_id, lease_key, url=None): """Marks build as STARTED. Idempotent. Args: build_id: id of the started build. lease_key: current lease key. url (str): a URL to a build-system-specific build, viewable by a human. Returns: The updated Build. """ validate_lease_key(lease_key) validate_url(url) build = self._get_leasable_build(build_id) if build.status == model.BuildStatus.STARTED: if build.url != url: build.url = url build.put() return build elif build.status == model.BuildStatus.COMPLETED: raise errors.BuildIsCompletedError( 'Cannot start a completed build') assert build.status == model.BuildStatus.SCHEDULED self._check_lease(build, lease_key) build.status = model.BuildStatus.STARTED build.status_changed_time = utils.utcnow() build.url = url build.put() logging.info('Build %s was started. URL: %s', build.key.id(), url) self._enqueue_callback_task_if_needed(build) return build
def get_build_async(check_access): build = yield model.Build.get_by_id_async(build_id) if build is None: raise errors.BuildNotFoundError() if check_access and not (yield user.can_cancel_build_async(build)): raise user.current_identity_cannot('cancel build %s', build.key.id()) if build.proto.status == common_pb2.CANCELED: raise ndb.Return(build, False) if build.is_ended: raise errors.BuildIsCompletedError( 'Cannot cancel a completed build') raise ndb.Return(build, True)
def txn(): build = _get_leasable_build(build_id) if not user.can_reset_build_async(build).get_result(): raise user.current_identity_cannot('reset build %s', build.key.id()) if build.is_ended: raise errors.BuildIsCompletedError( 'Cannot reset a completed build') build.proto.status = common_pb2.SCHEDULED build.status_changed_time = utils.utcnow() build.clear_lease() build.url = None _fut_results(build.put_async(), events.on_build_resetting_async(build)) return build
def txn(): validate_lease_key(lease_key) if lease_expiration_date is None: raise errors.InvalidInputError( 'Lease expiration date not specified') errors.validate_lease_expiration_date(lease_expiration_date) build = yield model.Build.get_by_id_async(build_id) if build is None: raise errors.BuildNotFoundError() if build.is_ended: msg = '' if (build.result == model.BuildResult.CANCELED and build.cancelation_reason == model.CancelationReason.TIMEOUT): msg = ('Build was marked as timed out ' 'because it did not complete for %s' % model.BUILD_TIMEOUT) raise errors.BuildIsCompletedError(msg) _check_lease(build, lease_key) build.lease_expiration_date = lease_expiration_date yield build.put_async() raise ndb.Return(build)
def reset(self, build_id): """Forcibly unleases the build and resets its state. Idempotent. Resets status, url and lease_key. Returns: The reset Build. """ build = self._get_leasable_build(build_id) if not acl.can_reset_build(build): raise current_identity_cannot('reset build %s', build.key.id()) if build.status == model.BuildStatus.COMPLETED: raise errors.BuildIsCompletedError( 'Cannot reset a completed build') build.status = model.BuildStatus.SCHEDULED build.status_changed_time = utils.utcnow() self._clear_lease(build) build.url = None build.put() logging.info('Build %s was reset by %s', build.key.id(), auth.get_current_identity().to_bytes()) return build
def test_cancel_batch(self, cancel): props = {'p': 0} build = test_util.build( id=1, output=dict(properties=bbutil.dict_to_struct(props))) cancel.side_effect = [ future(build), future_exception(errors.BuildIsCompletedError()), ] req = { 'build_ids': ['1', '2'], 'result_details_json': json.dumps(build.result_details), } res = self.call_api('cancel_batch', req).json_body res0 = res['results'][0] self.assertEqual(res0['build_id'], '1') self.assertEqual(res0['build']['id'], '1') cancel.assert_any_call(1, result_details=build.result_details) res1 = res['results'][1] self.assertEqual(res1['build_id'], '2') self.assertEqual(res1['error']['reason'], 'BUILD_IS_COMPLETED') cancel.assert_any_call(2, result_details=build.result_details)
def _complete(self, build_id, lease_key, result, result_details, failure_reason=None, url=None): """Marks a build as completed. Used by succeed and fail methods.""" validate_lease_key(lease_key) validate_url(url) assert result in (model.BuildResult.SUCCESS, model.BuildResult.FAILURE) build = self._get_leasable_build(build_id) if build.status == model.BuildStatus.COMPLETED: if (build.result == result and build.failure_reason == failure_reason and build.result_details == result_details and build.url == url): return build raise errors.BuildIsCompletedError( 'Build %s has already completed' % build_id) self._check_lease(build, lease_key) build.status = model.BuildStatus.COMPLETED build.status_changed_time = utils.utcnow() build.complete_time = utils.utcnow() build.result = result if url is not None: # pragma: no branch build.url = url build.result_details = result_details build.failure_reason = failure_reason self._clear_lease(build) build.put() logging.info('Build %s was completed. Status: %s. Result: %s', build.key.id(), build.status, build.result) self._enqueue_callback_task_if_needed(build) return build
def test_default_message(self): ex = errors.BuildIsCompletedError() self.assertEqual(ex.message, 'Build is complete and cannot be changed.')