def dist_git_upload_completed(): app.logger.debug(flask.request.json) build_id = flask.request.json.get("build_id") try: build = ComplexLogic.get_build_safe(build_id) except ObjectNotFound: return flask.jsonify({"updated": False}) collected_branch_chroots = [] for branch, git_hash in flask.request.json.get("branch_commits", {}).items(): branch_chroots = BuildsLogic.get_buildchroots_by_build_id_and_branch( build_id, branch) for ch in branch_chroots: ch.status = StatusEnum("pending") ch.git_hash = git_hash db.session.add(ch) collected_branch_chroots.append((ch.task_id)) final_source_status = StatusEnum("succeeded") for ch in build.build_chroots: if ch.task_id not in collected_branch_chroots: final_source_status = StatusEnum("failed") ch.status = StatusEnum("failed") db.session.add(ch) build.source_status = final_source_status db.session.add(build) db.session.commit() BuildsLogic.delete_local_source(build) return flask.jsonify({"updated": True})
def test_delete_build_exceptions(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): for bc in self.b4_bc: bc.status = StatusEnum("succeeded") bc.ended_on = time.time() self.u1.admin = False self.db.session.add_all(self.b4_bc) self.db.session.add(self.b4) self.db.session.add(self.u1) self.db.session.commit() with pytest.raises(InsufficientRightsException): BuildsLogic.delete_build(self.u1, self.b4) self.b1_bc[0].status = StatusEnum("running") self.db.session.add(self.b1_bc[0]) self.db.session.commit() with pytest.raises(ActionInProgressException): BuildsLogic.delete_build(self.u1, self.b1) self.copr_persistent = models.Copr(name=u"persistent_copr", user=self.u2, persistent=True) self.copr_dir = models.CoprDir(name="persistent_copr", main=True, copr=self.copr_persistent) self.build_persistent = models.Build(copr=self.copr_persistent, copr_dir=self.copr_dir, package=self.p2, user=self.u2, submitted_on=100) with pytest.raises(InsufficientRightsException): BuildsLogic.delete_build(self.u2, self.build_persistent)
def test_mark_as_failed(self, f_users, f_coprs, f_mock_chroots, f_builds): self.b1.source_status = StatusEnum("succeeded") self.db.session.commit() BuildsLogic.mark_as_failed(self.b1.id) BuildsLogic.mark_as_failed(self.b3.id) assert self.b1.status == StatusEnum("succeeded") assert self.b3.status == StatusEnum("failed") assert type(BuildsLogic.mark_as_failed(self.b3.id)) == models.Build
def mark_as_failed(cls, build_id): """ Marks build as failed on all its non-finished chroots """ build = cls.get(build_id).one() chroots = filter(lambda x: x.status != StatusEnum("succeeded"), build.build_chroots) for chroot in chroots: chroot.status = StatusEnum("failed") if build.source_status != StatusEnum("succeeded"): build.source_status = StatusEnum("failed") cls.process_update_callback(build) return build
def f_builds(self): self.p1 = models.Package( copr=self.c1, copr_dir=self.c1_dir, name="hello-world", source_type=0) self.p2 = models.Package( copr=self.c2, copr_dir=self.c2_dir, name="whatsupthere-world", source_type=0) self.p3 = models.Package( copr=self.c2, copr_dir=self.c3_dir, name="goodbye-world", source_type=0) self.b1 = models.Build( copr=self.c1, copr_dir=self.c1_dir, package=self.p1, user=self.u1, submitted_on=50, srpm_url="http://somesrpm", source_status=StatusEnum("succeeded"), result_dir='bar') self.b2 = models.Build( copr=self.c1, copr_dir=self.c1_dir, package=self.p1, user=self.u2, submitted_on=10, srpm_url="http://somesrpm", source_status=StatusEnum("importing"), result_dir='bar', source_json='{}') self.b3 = models.Build( copr=self.c2, copr_dir=self.c2_dir, package=self.p2, user=self.u2, submitted_on=10, srpm_url="http://somesrpm", source_status=StatusEnum("importing"), result_dir='bar') self.b4 = models.Build( copr=self.c2, copr_dir=self.c2_dir, package=self.p2, user=self.u2, submitted_on=100, srpm_url="http://somesrpm", source_status=StatusEnum("succeeded"), result_dir='bar') self.basic_builds = [self.b1, self.b2, self.b3, self.b4] self.b1_bc = [] self.b2_bc = [] self.b3_bc = [] self.b4_bc = [] for build, build_chroots in zip( [self.b1, self.b2, self.b3, self.b4], [self.b1_bc, self.b2_bc, self.b3_bc, self.b4_bc]): status = None if build is self.b1: # this build is going to be deleted status = StatusEnum("succeeded") for chroot in build.copr.active_chroots: buildchroot = models.BuildChroot( build=build, mock_chroot=chroot, status=status, git_hash="12345", result_dir='bar', ) if build is self.b1 or build is self.b2: buildchroot.started_on = 1390866440 buildchroot.ended_on = 1490866440 build_chroots.append(buildchroot) self.db.session.add(buildchroot) self.db.session.add_all([self.b1, self.b2, self.b3, self.b4])
def reschedule_build_chroot(): response = {} build_id = flask.request.json.get("build_id") task_id = flask.request.json.get("task_id") chroot = flask.request.json.get("chroot") try: build = ComplexLogic.get_build_safe(build_id) except ObjectNotFound: response["result"] = "noop" response["msg"] = "Build {} wasn't found".format(build_id) return flask.jsonify(response) if build.canceled: response["result"] = "noop" response["msg"] = "build was cancelled, ignoring" return flask.jsonify(response) run_statuses = set([StatusEnum("starting"), StatusEnum("running")]) if task_id == build.task_id: if build.source_status in run_statuses: log.info("rescheduling srpm build {}".format(build.id)) BuildsLogic.update_state_from_dict(build, { "task_id": task_id, "status": StatusEnum("pending") }) db.session.commit() response["result"] = "done" else: response["result"] = "noop" response["msg"] = "build is not in running states, ignoring" else: build_chroot = build.chroots_dict_by_name.get(chroot) if build_chroot and build_chroot.status in run_statuses: log.info("rescheduling build {} chroot: {}".format( build.id, build_chroot.name)) BuildsLogic.update_state_from_dict( build, { "task_id": task_id, "chroot": chroot, "status": StatusEnum("pending") }) db.session.commit() response["result"] = "done" else: response["result"] = "noop" response["msg"] = "build chroot is not in running states, ignoring" return flask.jsonify(response)
def f_pr_build(self, f_mock_chroots, f_builds, f_pr_dir): self.b_pr = models.Build( copr=self.c1, copr_dir=self.c4_dir, package=self.p1, user=self.u1, submitted_on=50, srpm_url="http://somesrpm", source_status=StatusEnum("succeeded"), result_dir='0000PR') self.bc_pr = models.BuildChroot( build=self.b_pr, mock_chroot=self.mc2, status=StatusEnum("succeeded"), git_hash="deadbeef", result_dir='0000PR-pr-package', ) self.db.session.add_all([self.b_pr, self.bc_pr])
def get_pending_build_tasks(cls, background=None): query = (models.BuildChroot.query.join(models.Build) .filter(models.Build.canceled == false()) .filter(or_( models.BuildChroot.status == StatusEnum("pending"), and_( models.BuildChroot.status == StatusEnum("running"), models.BuildChroot.started_on < int(time.time() - 1.1 * MAX_BUILD_TIMEOUT), models.BuildChroot.ended_on.is_(None) ) )) .order_by(models.Build.is_background.asc(), models.Build.id.asc())) if background is not None: query = query.filter(models.Build.is_background == (true() if background else false())) return query
def rebuild_package(cls, package, source_dict_update={}, copr_dir=None, update_callback=None, scm_object_type=None, scm_object_id=None, scm_object_url=None, submitted_by=None): source_dict = package.source_json_dict source_dict.update(source_dict_update) source_json = json.dumps(source_dict) if not copr_dir: copr_dir = package.copr.main_dir build = models.Build( user=None, pkgs=None, package=package, copr=package.copr, repos=package.copr.repos, source_status=StatusEnum("pending"), source_type=package.source_type, source_json=source_json, submitted_on=int(time.time()), enable_net=package.copr.build_enable_net, timeout=DEFAULT_BUILD_TIMEOUT, copr_dir=copr_dir, update_callback=update_callback, scm_object_type=scm_object_type, scm_object_id=scm_object_id, scm_object_url=scm_object_url, submitted_by=submitted_by, ) db.session.add(build) status = StatusEnum("waiting") for chroot in package.chroots: buildchroot = models.BuildChroot(build=build, status=status, mock_chroot=chroot, git_hash=None) db.session.add(buildchroot) cls.process_update_callback(build) return build
def api_status(): """ Receive information about queue """ output = { "importing": builds_logic.BuildsLogic.get_build_tasks( StatusEnum("importing")).count(), "waiting": builds_logic.BuildsLogic.get_build_tasks( StatusEnum("pending")).count(), # change to "pending"" "running": builds_logic.BuildsLogic.get_build_tasks( StatusEnum("running")).count(), } return flask.jsonify(output)
def test_build_put_cancel( self, f_users, f_coprs, f_mock_chroots, f_builds, f_users_api, ): for bc in self.b1_bc: bc.status = StatusEnum("pending") bc.ended_on = None self.db.session.add_all(self.b1_bc) self.db.session.add(self.b1) self.db.session.commit() href = "/api_2/builds/{}".format(self.b1.id) build_dict = {"state": "canceled"} r = self.request_rest_api_with_auth(href, method="put", content=build_dict) assert r.status_code == 201 r2 = self.tc.get(r.headers["Location"]) assert r2.status_code == 200 obj = json.loads(r2.data.decode("utf-8")) assert obj["build"]["state"] == "canceled"
def test_collection_ok_by_state( self, f_users, f_coprs, f_mock_chroots, f_mock_chroots_many, f_build_many_chroots, f_db, f_users_api): self.db.session.commit() for status in StatusEnum.vals.values(): expected_chroots = set([ name for name, chroot_status in self.status_by_chroot.items() if chroot_status == status ]) href = "/api_2/build_tasks?state={}&limit=50".format(StatusEnum(status)) r0 = self.tc.get(href) assert r0.status_code == 200 obj = json.loads(r0.data.decode("utf-8")) assert len(obj["build_tasks"]) == len(expected_chroots) assert set(bt["build_task"]["chroot_name"] for bt in obj["build_tasks"]) == expected_chroots assert parse_qs(urlparse(obj["_links"]["self"]["href"]).query) \ == parse_qs(urlparse(href).query)
def last_modified(cls, copr): """ Get build datetime (as epoch) of last successful build :arg copr: object of copr """ builds = cls.get_multiple_by_copr(copr) last_build = (builds.join(models.BuildChroot).filter( (models.BuildChroot.status == StatusEnum("succeeded")) | (models.BuildChroot.status == StatusEnum("skipped"))).filter( models.BuildChroot.ended_on.isnot(None)).order_by( models.BuildChroot.ended_on.desc())).first() if last_build: return last_build.ended_on else: return None
def test_delete_build_as_admin(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): self.b4.pkgs = "http://example.com/copr-keygen-1.58-1.fc20.src.rpm" expected_dir = self.b4.result_dir for bc in self.b4_bc: bc.status = StatusEnum("succeeded") bc.ended_on = time.time() self.u1.admin = True self.db.session.add_all(self.b4_bc) self.db.session.add(self.b4) self.db.session.add(self.u1) self.db.session.commit() expected_chroots_to_delete = set() for bchroot in self.b4_bc: expected_chroots_to_delete.add(bchroot.name) assert len(ActionsLogic.get_many().all()) == 0 BuildsLogic.delete_build(self.u1, self.b4) self.db.session.commit() assert len(ActionsLogic.get_many().all()) == 1 action = ActionsLogic.get_many().one() with pytest.raises(NoResultFound): BuildsLogic.get(self.b4.id).one()
def test_build_put_wrong_user( self, f_users, f_coprs, f_mock_chroots, f_builds, f_users_api, ): login = self.u2.api_login token = self.u2.api_token for bc in self.b1_bc: bc.status = StatusEnum("pending") bc.ended_on = None self.u1.admin = False self.db.session.add_all(self.b1_bc) self.db.session.add(self.b1) self.db.session.commit() href = "/api_2/builds/{}".format(self.b1.id) build_dict = {"state": "canceled"} r = self.request_rest_api_with_auth(href, method="put", login=login, token=token, content=build_dict) assert r.status_code == 403
def test_copr_build_submitter_can_delete_build_old(self, f_users, f_coprs, f_build_few_chroots, f_db): self.db.session.add_all([self.u1, self.c1, self.b_few_chroots]) self.b_few_chroots.build_chroots[1].status= StatusEnum("canceled") self.db.session.commit() expected_chroot_builddirs = {'srpm-builds': [self.b_few_chroots.result_dir]} for chroot in self.b_few_chroots.build_chroots: expected_chroot_builddirs[chroot.name] = [chroot.result_dir] expected_dir = self.b_few_chroots.result_dir r = self.test_client.post( "/coprs/{0}/{1}/delete_build/{2}/" .format(self.u1.name, self.c1.name, self.b_few_chroots.id), data={}, follow_redirects=True) assert b"Build has been deleted" in r.data b = (self.models.Build.query.filter( self.models.Build.id == self.b_few_chroots.id).first()) assert b is None act = self.models.Action.query.first() data = json.loads(act.data) assert act.object_type == "build" assert data.get('ownername') == "user1" assert data.get('projectname') == "foocopr" assert json.loads(act.data)["chroot_builddirs"] == expected_chroot_builddirs
def fork_build(self, build, fcopr, fpackage): fbuild = self.create_object(models.Build, build, exclude=[ "id", "copr_id", "copr_dir_id", "package_id", "result_dir" ]) fbuild.copr = fcopr fbuild.package = fpackage fbuild.copr_dir = fcopr.main_dir db.session.add(fbuild) db.session.flush() fbuild.result_dir = '{:08}'.format(fbuild.id) fbuild.build_chroots = [ self.create_object(models.BuildChroot, c, exclude=["id", "build_id", "result_dir"]) for c in build.build_chroots ] for chroot in fbuild.build_chroots: chroot.result_dir = '{:08}-{}'.format(fbuild.id, fpackage.name) chroot.status = StatusEnum("forked") db.session.add(fbuild) return fbuild
def to_source_chroot(build): return { "state": StatusEnum(build.source_status), "result_url": os.path.dirname(build.import_log_url_backend), # @TODO Do we have such information stored? # "started_on": None, # "ended_on": None }
def get_pending_srpm_build_tasks(cls, background=None): query = (models.Build.query .filter(models.Build.canceled == false()) .filter(models.Build.source_status == StatusEnum("pending")) .order_by(models.Build.is_background.asc(), models.Build.id.asc())) if background is not None: query = query.filter(models.Build.is_background == (true() if background else false())) return query
def test_state(self, f_users, f_coprs, f_mock_chroots, f_builds, f_modules, f_db): self.b1.build_chroots[0].status = StatusEnum("pending") self.b3.build_chroots[0].status = StatusEnum("succeeded") self.b3.build_chroots[1].status = StatusEnum("succeeded") self.b3.source_status = StatusEnum("succeeded") # even though b3 is succeeded, b1 is still pending self.m1.builds = [self.b1, self.b3] assert self.m1.status == ModuleStatusEnum("pending") # now what if b1 succeeds self.b1.build_chroots[0].status = StatusEnum("succeeded") assert self.m1.status == ModuleStatusEnum("succeeded") # let's say that b3 failed self.b3.build_chroots[0].status = StatusEnum("failed") assert self.m1.status == ModuleStatusEnum("failed") # once the action exists, it dictates the status self.b3.build_chroots[0].status = StatusEnum("succeeded") action = models.Action( action_type=ActionTypeEnum("build_module"), object_type="module", object_id=self.m1.id, ) db.session.add(action) assert self.m1.status == ModuleStatusEnum("waiting") # the backend proceeds the action action.result = BackendResultEnum("success") assert self.m1.status == ModuleStatusEnum("succeeded")
def process_copr_repeat_build(build_id, copr): build = ComplexLogic.get_build_safe(build_id) if not flask.g.user.can_build_in(build.copr): flask.flash("You are not allowed to repeat this build.") if build.source_type == helpers.BuildSourceEnum('upload'): # If the original build's source is 'upload', we will show only the # original build's chroots and skip import. available_chroots = build.chroots else: # For all other sources, we will show all chroots enabled in the project # and proceed with import. available_chroots = copr.active_chroots form = forms.BuildFormRebuildFactory.create_form_cls(available_chroots)( build_id=build_id, enable_net=build.enable_net) # remove all checkboxes by default for ch in available_chroots: field = getattr(form, ch.name) field.data = False chroot_to_build = request.args.get("chroot") app.logger.debug("got param chroot: {}".format(chroot_to_build)) if chroot_to_build: # set single checkbox if chroot query arg was provided if hasattr(form, chroot_to_build): getattr(form, chroot_to_build).data = True else: build_chroot_names = set(ch.name for ch in build.chroots) build_failed_chroot_names = set(ch.name for ch in build.get_chroots_by_status([ StatusEnum('failed'), StatusEnum('canceled'), ])) for ch in available_chroots: # check checkbox on all the chroots that have not been (successfully) built before if (ch.name not in build_chroot_names) or ( ch.name in build_failed_chroot_names): getattr(form, ch.name).data = True return flask.render_template("coprs/detail/add_build/rebuild.html", copr=copr, build=build, form=form)
def last_successful_build_chroots(cls, package): builds = {} for chroot in package.chroots: for build in reversed(package.builds): try: build_chroot = build.chroots_dict_by_name[chroot.name] except KeyError: continue if build_chroot.status not in [ StatusEnum("succeeded"), StatusEnum("forked") ]: continue if build not in builds: builds[build] = [build_chroot] else: builds[build].append(build_chroot) break return builds
def test_build_queue_4(self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): time_now = int(time.time()) for build_chroots in [self.b1_bc, self.b2_bc]: for build_chroot in build_chroots: build_chroot.status = StatusEnum("running") build_chroot.started_on = time_now - 2 * MAX_BUILD_TIMEOUT build_chroot.ended_on = None for build_chroots in [self.b3_bc, self.b4_bc]: for build_chroot in build_chroots: build_chroot.status = StatusEnum("failed") build_chroot.started_on = time_now - 2 * MAX_BUILD_TIMEOUT build_chroot.ended_on = None self.db.session.commit() data = BuildsLogic.get_pending_build_tasks().all() assert len(data) == 2 assert set([data[0], data[1]]) == set([self.b1_bc[0], self.b2_bc[0]])
def get_queue_sizes(): importing = BuildsLogic.get_build_importing_queue( background=False).count() pending = BuildsLogic.get_pending_build_tasks(background=False).count() running = BuildsLogic.get_build_tasks(StatusEnum("running")).count() return dict( importing=importing, pending=pending, running=running, )
def get_build_importing_queue(cls, background=None): """ Returns Builds which are waiting to be uploaded to dist git """ query = (models.Build.query .filter(models.Build.canceled == false()) .filter(models.Build.source_status == StatusEnum("importing")) .order_by(models.Build.id.asc())) if background is not None: query = query.filter(models.Build.is_background == (true() if background else false())) return query
def get_pending_build_tasks(cls, background=None): query = (models.BuildChroot.query.outerjoin(models.Build).outerjoin( models.CoprDir).outerjoin( models.Package, models.Package.id == models.Build.package_id).options( joinedload('build').joinedload('copr_dir'), joinedload('build').joinedload('package') ).filter(models.Build.canceled == false()).filter( or_( models.BuildChroot.status == StatusEnum("pending"), and_( models.BuildChroot.status == StatusEnum("running"), models.BuildChroot.started_on < int(time.time() - 1.1 * MAX_BUILD_TIMEOUT), models.BuildChroot.ended_on.is_(None)))).order_by( models.Build.is_background.asc(), models.Build.id.asc())) if background is not None: query = query.filter(models.Build.is_background == ( true() if background else false())) return query
def cancel_build(cls, user, build): if not user.can_build_in(build.copr): raise InsufficientRightsException( "You are not allowed to cancel this build.") if not build.cancelable: if build.status == StatusEnum("starting"): # this is not intuitive, that's why we provide more specific message err_msg = "Cannot cancel build {} in state 'starting'".format(build.id) else: err_msg = "Cannot cancel build {}".format(build.id) raise RequestCannotBeExecuted(err_msg) if build.status == StatusEnum("running"): # otherwise the build is just in frontend ActionsLogic.send_cancel_build(build) build.canceled = True cls.process_update_callback(build) for chroot in build.build_chroots: chroot.status = 2 # canceled if chroot.ended_on is not None: chroot.ended_on = time.time()
def get_running_jobs_bucket(cls, start, end): query = text(""" SELECT COUNT(*) as result FROM build_chroot WHERE started_on < :end AND (ended_on > :start OR (ended_on is NULL AND status = :status)) -- for currently running builds we need to filter on status=running because there might be failed -- builds that have ended_on=NULL """) res = db.engine.execute(query, start=start, end=end, status=StatusEnum("running")) return res.first().result
def test_copr_build_non_submitter_cannot_cancel_build( self, f_users, f_coprs, f_mock_chroots, f_builds, f_db): for bc in self.b1_bc: bc.status = StatusEnum("pending") bc.ended_on = None self.u1.admin = False self.db.session.add_all(self.b1_bc) self.db.session.add_all([self.u1, self.c1, self.b1]) self.test_client.post("/coprs/{0}/{1}/cancel_build/{2}/".format( self.u1.name, self.c1.name, self.b1.id), data={}, follow_redirects=True) assert self.models.Build.query.first().canceled is False
def test_pending_blocked_builds(self, f_users, f_coprs, f_mock_chroots, f_builds, f_batches, f_db): for build in [self.b2, self.b3, self.b4]: build.source_status = StatusEnum("pending") self.b2.batch = self.batch2 self.b3.batch = self.batch3 self.batch3.blocked_by = self.batch2 self.db.session.commit() r = self.tc.get("/backend/pending-jobs/") data = json.loads(r.data.decode("utf-8")) ids = [job["build_id"] for job in data] assert self.b3.id not in ids assert {self.b2.id, self.b4.id}.issubset(ids)