Package coprs :: Package logic :: Module builds_logic
[hide private]
[frames] | no frames]

Source Code for Module coprs.logic.builds_logic

  1  import tempfile 
  2  import shutil 
  3  import json 
  4  import os 
  5  import pprint 
  6  import time 
  7  import flask 
  8  import sqlite3 
  9  from sqlalchemy.sql import text 
 10  from sqlalchemy import or_ 
 11  from sqlalchemy import and_ 
 12  from sqlalchemy.orm import joinedload 
 13  from sqlalchemy.orm.exc import NoResultFound 
 14  from sqlalchemy.sql import false,true 
 15  from werkzeug.utils import secure_filename 
 16  from sqlalchemy import desc,asc, bindparam, Integer 
 17  from collections import defaultdict 
 18   
 19  from coprs import app 
 20  from coprs import db 
 21  from coprs import exceptions 
 22  from coprs import models 
 23  from coprs import helpers 
 24  from coprs.constants import DEFAULT_BUILD_TIMEOUT, MAX_BUILD_TIMEOUT, DEFER_BUILD_SECONDS 
 25  from coprs.exceptions import MalformedArgumentException, ActionInProgressException, InsufficientRightsException 
 26  from coprs.helpers import StatusEnum 
 27   
 28  from coprs.logic import coprs_logic 
 29  from coprs.logic import users_logic 
 30  from coprs.logic.actions_logic import ActionsLogic 
 31  from coprs.models import BuildChroot,Build,Package,MockChroot 
 32  from .coprs_logic import MockChrootsLogic 
 33   
 34  log = app.logger 
35 36 37 -class BuildsLogic(object):
38 @classmethod
39 - def get(cls, build_id):
40 return models.Build.query.filter(models.Build.id == build_id)
41 42 # todo: move methods operating with BuildChroot to BuildChrootLogic 43 @classmethod
44 - def get_build_tasks(cls, status, background=None):
45 """ Returns tasks with given status. If background is specified then 46 returns normal jobs (false) or background jobs (true) 47 """ 48 result = models.BuildChroot.query.join(models.Build)\ 49 .filter(models.BuildChroot.status == status)\ 50 .order_by(models.BuildChroot.build_id.asc()) 51 if background is not None: 52 result = result.filter(models.Build.is_background == (true() if background else false())) 53 return result
54 55 @classmethod
56 - def get_recent_tasks(cls, user=None, limit=None):
57 if not limit: 58 limit = 100 59 60 query = models.Build.query 61 if user is not None: 62 query = query.filter(models.Build.user_id == user.id) 63 64 query = query.join( 65 models.BuildChroot.query 66 .filter(models.BuildChroot.ended_on.isnot(None)) 67 .order_by(models.BuildChroot.ended_on.desc()) 68 .subquery() 69 ).order_by(models.Build.id.desc()) 70 71 # Workaround - otherwise it could take less records than `limit`even though there are more of them. 72 query = query.limit(limit if limit > 100 else 100) 73 return list(query.all()[:5])
74 75 @classmethod
77 """ 78 Returns Builds which are waiting to be uploaded to dist git 79 """ 80 query = (models.Build.query.join(models.BuildChroot) 81 .filter(models.Build.canceled == false()) 82 .filter(models.BuildChroot.status == helpers.StatusEnum("importing")) 83 ) 84 query = query.order_by(models.BuildChroot.build_id.asc()) 85 return query
86 87 @classmethod
88 - def get_build_task_queue(cls, is_background=False): # deprecated
89 """ 90 Returns BuildChroots which are - waiting to be built or 91 - older than 2 hours and unfinished 92 """ 93 # todo: filter out build without package 94 query = (models.BuildChroot.query.join(models.Build) 95 .filter(models.Build.canceled == false()) 96 .filter(models.Build.is_background == (true() if is_background else false())) 97 .filter(or_( 98 models.BuildChroot.status == helpers.StatusEnum("pending"), 99 models.BuildChroot.status == helpers.StatusEnum("starting"), 100 and_( 101 # We are moving ended_on to the BuildChroot, now it should be reliable, 102 # so we don't want to reschedule failed chroots 103 # models.BuildChroot.status.in_([ 104 # # Bug 1206562 - Cannot delete Copr because it incorrectly thinks 105 # # there are unfinished builds. Solution: `failed` but unfinished 106 # # (ended_on is null) builds should be rescheduled. 107 # # todo: we need to be sure that correct `failed` set is set together wtih `ended_on` 108 # helpers.StatusEnum("running"), 109 # helpers.StatusEnum("failed") 110 #]), 111 models.BuildChroot.status == helpers.StatusEnum("running"), 112 models.BuildChroot.started_on < int(time.time() - 1.1 * MAX_BUILD_TIMEOUT), 113 models.BuildChroot.ended_on.is_(None) 114 )) 115 )) 116 query = query.order_by(models.BuildChroot.build_id.asc()) 117 return query
118 119 @classmethod
120 - def select_build_task(cls):
121 query = (models.BuildChroot.query.join(models.Build) 122 .filter(models.Build.canceled == false()) 123 .filter(or_( 124 models.BuildChroot.status == helpers.StatusEnum("pending"), 125 and_( 126 models.BuildChroot.status == helpers.StatusEnum("running"), 127 models.BuildChroot.started_on < int(time.time() - 1.1 * MAX_BUILD_TIMEOUT), 128 models.BuildChroot.ended_on.is_(None) 129 ) 130 )) 131 .filter(or_( 132 models.BuildChroot.last_deferred.is_(None), 133 models.BuildChroot.last_deferred < int(time.time() - DEFER_BUILD_SECONDS) 134 )) 135 ).order_by(models.Build.is_background.asc(), models.BuildChroot.build_id.asc()) 136 return query.first()
137 138 @classmethod
139 - def get_build_task(cls, task_id):
140 try: 141 build_id, chroot_name = task_id.split("-", 1) 142 except ValueError: 143 raise MalformedArgumentException("Invalid task_id {}".format(task_id)) 144 145 build_chroot = BuildChrootsLogic.get_by_build_id_and_name(build_id, chroot_name) 146 return build_chroot.join(models.Build).first()
147 148 149 @classmethod
150 - def get_multiple(cls):
151 return models.Build.query.order_by(models.Build.id.desc())
152 153 @classmethod
154 - def get_multiple_by_copr(cls, copr):
155 """ Get collection of builds in copr sorted by build_id descending 156 """ 157 return cls.get_multiple().filter(models.Build.copr == copr)
158 159 @classmethod
160 - def get_multiple_by_user(cls, user):
161 """ Get collection of builds in copr sorted by build_id descending 162 form the copr belonging to `user` 163 """ 164 return cls.get_multiple().join(models.Build.copr).filter( 165 models.Copr.user == user)
166 167 168 @classmethod
169 - def init_db(cls):
170 if db.engine.url.drivername == "sqlite": 171 return 172 173 status_to_order = """ 174 CREATE OR REPLACE FUNCTION status_to_order (x integer) 175 RETURNS integer AS $$ BEGIN 176 RETURN CASE WHEN x = 0 THEN 0 177 WHEN x = 3 THEN 1 178 WHEN x = 6 THEN 2 179 WHEN x = 7 THEN 3 180 WHEN x = 4 THEN 4 181 WHEN x = 1 THEN 5 182 WHEN x = 5 THEN 6 183 ELSE 1000 184 END; END; 185 $$ LANGUAGE plpgsql; 186 """ 187 188 order_to_status = """ 189 CREATE OR REPLACE FUNCTION order_to_status (x integer) 190 RETURNS integer AS $$ BEGIN 191 RETURN CASE WHEN x = 0 THEN 0 192 WHEN x = 1 THEN 3 193 WHEN x = 2 THEN 6 194 WHEN x = 3 THEN 7 195 WHEN x = 4 THEN 4 196 WHEN x = 5 THEN 1 197 WHEN x = 6 THEN 5 198 ELSE 1000 199 END; END; 200 $$ LANGUAGE plpgsql; 201 """ 202 203 db.engine.connect() 204 db.engine.execute(status_to_order) 205 db.engine.execute(order_to_status)
206 207 @classmethod
208 - def get_copr_builds_list(cls, copr):
209 query_select = """ 210 SELECT build.id, MAX(package.name) AS pkg_name, build.pkg_version, build.submitted_on, 211 MIN(statuses.started_on) AS started_on, MAX(statuses.ended_on) AS ended_on, order_to_status(MIN(statuses.st)) AS status, 212 build.canceled, MIN("group".name) AS group_name, MIN(copr.name) as copr_name, MIN("user".username) as user_name 213 FROM build 214 LEFT OUTER JOIN package 215 ON build.package_id = package.id 216 LEFT OUTER JOIN (SELECT build_chroot.build_id, started_on, ended_on, status_to_order(status) AS st FROM build_chroot) AS statuses 217 ON statuses.build_id=build.id 218 LEFT OUTER JOIN copr 219 ON copr.id = build.copr_id 220 LEFT OUTER JOIN "user" 221 ON copr.user_id = "user".id 222 LEFT OUTER JOIN "group" 223 ON copr.group_id = "group".id 224 WHERE build.copr_id = :copr_id 225 GROUP BY 226 build.id; 227 """ 228 229 if db.engine.url.drivername == "sqlite": 230 def sqlite_status_to_order(x): 231 if x == 3: 232 return 1 233 elif x == 6: 234 return 2 235 elif x == 7: 236 return 3 237 elif x == 4: 238 return 4 239 elif x == 0: 240 return 5 241 elif x == 1: 242 return 6 243 elif x == 5: 244 return 7 245 elif x == 8: 246 return 8 247 return 1000
248 249 def sqlite_order_to_status(x): 250 if x == 1: 251 return 3 252 elif x == 2: 253 return 6 254 elif x == 3: 255 return 7 256 elif x == 4: 257 return 4 258 elif x == 5: 259 return 0 260 elif x == 6: 261 return 1 262 elif x == 7: 263 return 5 264 elif x == 8: 265 return 8 266 return 1000 267 268 conn = db.engine.connect() 269 conn.connection.create_function("status_to_order", 1, sqlite_status_to_order) 270 conn.connection.create_function("order_to_status", 1, sqlite_order_to_status) 271 statement = text(query_select) 272 statement.bindparams(bindparam("copr_id", Integer)) 273 result = conn.execute(statement, {"copr_id": copr.id}) 274 else: 275 statement = text(query_select) 276 statement.bindparams(bindparam("copr_id", Integer)) 277 result = db.engine.execute(statement, {"copr_id": copr.id}) 278 279 return result 280 281 @classmethod
282 - def join_group(cls, query):
283 return query.join(models.Copr).outerjoin(models.Group)
284 285 @classmethod
286 - def get_multiple_by_name(cls, username, coprname):
287 query = cls.get_multiple() 288 return (query.join(models.Build.copr) 289 .options(db.contains_eager(models.Build.copr)) 290 .join(models.Copr.user) 291 .filter(models.Copr.name == coprname) 292 .filter(models.User.username == username))
293 294 @classmethod
295 - def get_importing(cls):
296 """ 297 Return builds that are waiting for dist git to import the sources. 298 """ 299 query = (models.Build.query.join(models.Build.copr) 300 .join(models.User) 301 .join(models.BuildChroot) 302 .options(db.contains_eager(models.Build.copr)) 303 .options(db.contains_eager("copr.user")) 304 .filter((models.BuildChroot.started_on == None) 305 | (models.BuildChroot.started_on < int(time.time() - 7200))) 306 .filter(models.BuildChroot.ended_on == None) 307 .filter(models.Build.canceled == False) 308 .order_by(models.Build.submitted_on.asc())) 309 return query
310 311 @classmethod
312 - def get_waiting(cls):
313 """ 314 Return builds that aren't both started and finished 315 (if build start submission fails, we still want to mark 316 the build as non-waiting, if it ended) 317 this has very different goal then get_multiple, so implement it alone 318 """ 319 320 query = (models.Build.query.join(models.Build.copr) 321 .join(models.User).join(models.BuildChroot) 322 .options(db.contains_eager(models.Build.copr)) 323 .options(db.contains_eager("copr.user")) 324 .filter((models.BuildChroot.started_on.is_(None)) 325 | (models.BuildChroot.started_on < int(time.time() - 7200))) 326 .filter(models.BuildChroot.ended_on.is_(None)) 327 .filter(models.Build.canceled == false()) 328 .order_by(models.Build.submitted_on.asc())) 329 return query
330 331 @classmethod
332 - def get_by_ids(cls, ids):
333 return models.Build.query.filter(models.Build.id.in_(ids))
334 335 @classmethod
336 - def get_by_id(cls, build_id):
337 return models.Build.query.filter(models.Build.id == build_id)
338 339 @classmethod
340 - def create_new_from_other_build(cls, user, copr, source_build, 341 chroot_names=None, **build_options):
342 skip_import = False 343 git_hashes = {} 344 345 if source_build.source_type == helpers.BuildSourceEnum('upload'): 346 # I don't have the source 347 # so I don't want to import anything, just rebuild what's in dist git 348 skip_import = True 349 350 for chroot in source_build.build_chroots: 351 if not chroot.git_hash: 352 # I got an old build from time we didn't use dist git 353 # So I'll submit it as a new build using it's link 354 skip_import = False 355 git_hashes = None 356 flask.flash("This build is not in Dist Git. Trying to import the package again.") 357 break 358 git_hashes[chroot.name] = chroot.git_hash 359 360 build = cls.create_new(user, copr, source_build.source_type, source_build.source_json, chroot_names, 361 pkgs=source_build.pkgs, git_hashes=git_hashes, skip_import=skip_import, **build_options) 362 build.package_id = source_build.package_id 363 build.pkg_version = source_build.pkg_version 364 return build
365 366 @classmethod
367 - def create_new_from_url(cls, user, copr, url, 368 chroot_names=None, **build_options):
369 """ 370 :type user: models.User 371 :type copr: models.Copr 372 373 :type chroot_names: List[str] 374 375 :rtype: models.Build 376 """ 377 source_type = helpers.BuildSourceEnum("link") 378 source_json = json.dumps({"url": url}) 379 return cls.create_new(user, copr, source_type, source_json, chroot_names, pkgs=url, **build_options)
380 381 @classmethod
382 - def create_new_from_tito(cls, user, copr, git_url, git_dir, git_branch, tito_test, 383 chroot_names=None, **build_options):
384 """ 385 :type user: models.User 386 :type copr: models.Copr 387 388 :type chroot_names: List[str] 389 390 :rtype: models.Build 391 """ 392 source_type = helpers.BuildSourceEnum("git_and_tito") 393 source_json = json.dumps({"git_url": git_url, 394 "git_dir": git_dir, 395 "git_branch": git_branch, 396 "tito_test": tito_test}) 397 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
398 399 @classmethod
400 - def create_new_from_mock(cls, user, copr, scm_type, scm_url, scm_branch, scm_subdir, spec, 401 chroot_names=None, **build_options):
402 """ 403 :type user: models.User 404 :type copr: models.Copr 405 406 :type chroot_names: List[str] 407 408 :rtype: models.Build 409 """ 410 source_type = helpers.BuildSourceEnum("mock_scm") 411 source_json = json.dumps({"scm_type": scm_type, 412 "scm_url": scm_url, 413 "scm_branch": scm_branch, 414 "scm_subdir": scm_subdir, 415 "spec": spec}) 416 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
417 418 @classmethod
419 - def create_new_from_pypi(cls, user, copr, pypi_package_name, pypi_package_version, python_versions, 420 chroot_names=None, **build_options):
421 """ 422 :type user: models.User 423 :type copr: models.Copr 424 :type package_name: str 425 :type version: str 426 :type python_versions: List[str] 427 428 :type chroot_names: List[str] 429 430 :rtype: models.Build 431 """ 432 source_type = helpers.BuildSourceEnum("pypi") 433 source_json = json.dumps({"pypi_package_name": pypi_package_name, 434 "pypi_package_version": pypi_package_version, 435 "python_versions": python_versions}) 436 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
437 438 @classmethod
439 - def create_new_from_rubygems(cls, user, copr, gem_name, 440 chroot_names=None, **build_options):
441 """ 442 :type user: models.User 443 :type copr: models.Copr 444 :type gem_name: str 445 :type chroot_names: List[str] 446 :rtype: models.Build 447 """ 448 source_type = helpers.BuildSourceEnum("rubygems") 449 source_json = json.dumps({"gem_name": gem_name}) 450 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
451 452 @classmethod
453 - def create_new_from_distgit(cls, user, copr, clone_url, branch, 454 chroot_names=None, **build_options):
455 """ 456 :type user: models.User 457 :type copr: models.Copr 458 :type clone_url: str 459 :type branch: str 460 :type chroot_names: List[str] 461 :rtype: models.Build 462 """ 463 source_type = helpers.BuildSourceEnum("distgit") 464 source_json = json.dumps({ 465 "clone_url": clone_url, 466 "branch": branch 467 }) 468 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
469 470 @classmethod
471 - def create_new_from_upload(cls, user, copr, f_uploader, orig_filename, 472 chroot_names=None, **build_options):
473 """ 474 :type user: models.User 475 :type copr: models.Copr 476 :param f_uploader(file_path): function which stores data at the given `file_path` 477 :return: 478 """ 479 tmp = tempfile.mkdtemp(dir=app.config["SRPM_STORAGE_DIR"]) 480 tmp_name = os.path.basename(tmp) 481 filename = secure_filename(orig_filename) 482 file_path = os.path.join(tmp, filename) 483 f_uploader(file_path) 484 485 # make the pkg public 486 pkg_url = "{baseurl}/tmp/{tmp_dir}/{filename}".format( 487 baseurl=app.config["PUBLIC_COPR_BASE_URL"], 488 tmp_dir=tmp_name, 489 filename=filename) 490 491 # create json describing the build source 492 source_type = helpers.BuildSourceEnum("upload") 493 source_json = json.dumps({"url": pkg_url, "pkg": filename, "tmp": tmp_name}) 494 try: 495 build = cls.create_new(user, copr, source_type, source_json, 496 chroot_names, pkgs=pkg_url, **build_options) 497 except Exception: 498 shutil.rmtree(tmp) # todo: maybe we should delete in some cleanup procedure? 499 raise 500 501 return build
502 503 @classmethod
504 - def create_new(cls, user, copr, source_type, source_json, chroot_names=None, 505 pkgs="", git_hashes=None, skip_import=False, background=False, **build_options):
506 """ 507 :type user: models.User 508 :type copr: models.Copr 509 :type chroot_names: List[str] 510 :type source_type: int value from helpers.BuildSourceEnum 511 :type source_json: str in json format 512 :type pkgs: str 513 :type git_hashes: dict 514 :type skip_import: bool 515 :type background: bool 516 :rtype: models.Build 517 """ 518 if chroot_names is None: 519 chroots = [c for c in copr.active_chroots] 520 else: 521 chroots = [] 522 for chroot in copr.active_chroots: 523 if chroot.name in chroot_names: 524 chroots.append(chroot) 525 526 build = cls.add( 527 user=user, 528 pkgs=pkgs, 529 copr=copr, 530 chroots=chroots, 531 source_type=source_type, 532 source_json=source_json, 533 enable_net=build_options.get("enable_net", copr.build_enable_net), 534 background=background, 535 git_hashes=git_hashes, 536 skip_import=skip_import) 537 538 if user.proven: 539 if "timeout" in build_options: 540 build.timeout = build_options["timeout"] 541 542 return build
543 544 @classmethod
545 - def add(cls, user, pkgs, copr, source_type=None, source_json=None, 546 repos=None, chroots=None, timeout=None, enable_net=True, 547 git_hashes=None, skip_import=False, background=False):
548 if chroots is None: 549 chroots = [] 550 551 coprs_logic.CoprsLogic.raise_if_unfinished_blocking_action( 552 copr, "Can't build while there is an operation in progress: {action}") 553 users_logic.UsersLogic.raise_if_cant_build_in_copr( 554 user, copr, 555 "You don't have permissions to build in this copr.") 556 557 if not repos: 558 repos = copr.repos 559 560 # todo: eliminate pkgs and this check 561 if pkgs and (" " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs): 562 raise exceptions.MalformedArgumentException("Trying to create a build using src_pkg " 563 "with bad characters. Forgot to split?") 564 565 # just temporary to keep compatibility 566 if not source_type or not source_json: 567 source_type = helpers.BuildSourceEnum("link") 568 source_json = json.dumps({"url":pkgs}) 569 570 build = models.Build( 571 user=user, 572 pkgs=pkgs, 573 copr=copr, 574 repos=repos, 575 source_type=source_type, 576 source_json=source_json, 577 submitted_on=int(time.time()), 578 enable_net=bool(enable_net), 579 is_background=bool(background), 580 ) 581 582 if timeout: 583 build.timeout = timeout or DEFAULT_BUILD_TIMEOUT 584 585 db.session.add(build) 586 587 # add BuildChroot object for each active (or selected) chroot 588 # this copr is assigned to 589 if not chroots: 590 chroots = copr.active_chroots 591 592 status = helpers.StatusEnum("importing") 593 594 if skip_import: 595 status = StatusEnum("pending") 596 597 for chroot in chroots: 598 git_hash = None 599 if git_hashes: 600 git_hash = git_hashes.get(chroot.name) 601 buildchroot = models.BuildChroot( 602 build=build, 603 status=status, 604 mock_chroot=chroot, 605 git_hash=git_hash) 606 607 db.session.add(buildchroot) 608 609 return build
610 611 @classmethod
612 - def rebuild_package(cls, package):
613 build = models.Build( 614 user=None, 615 pkgs=None, 616 package_id=package.id, 617 copr=package.copr, 618 repos=package.copr.repos, 619 source_type=package.source_type, 620 source_json=package.source_json, 621 submitted_on=int(time.time()), 622 enable_net=package.copr.build_enable_net, 623 timeout=DEFAULT_BUILD_TIMEOUT 624 ) 625 626 db.session.add(build) 627 628 chroots = package.copr.active_chroots 629 630 status = helpers.StatusEnum("importing") 631 632 for chroot in chroots: 633 buildchroot = models.BuildChroot( 634 build=build, 635 status=status, 636 mock_chroot=chroot, 637 git_hash=None 638 ) 639 640 db.session.add(buildchroot) 641 642 return build
643 644 645 terminal_states = {StatusEnum("failed"), StatusEnum("succeeded"), StatusEnum("canceled")} 646 647 @classmethod
648 - def get_buildchroots_by_build_id_and_branch(cls, build_id, branch):
649 """ 650 Returns a list of BuildChroots identified by build_id and dist-git 651 branch name. 652 """ 653 return ( 654 models.BuildChroot.query 655 .join(models.MockChroot) 656 .filter(models.BuildChroot.build_id==build_id) 657 .filter(models.MockChroot.distgit_branch_name==branch) 658 ).all()
659 660 661 @classmethod
662 - def delete_local_source(cls, build):
663 """ 664 Deletes the source (rpm or .spec) locally stored for upload (if exists) 665 """ 666 # is it hosted on the copr frontend? 667 if build.source_type == helpers.BuildSourceEnum("upload"): 668 data = json.loads(build.source_json) 669 tmp = data["tmp"] 670 storage_path = app.config["SRPM_STORAGE_DIR"] 671 try: 672 shutil.rmtree(os.path.join(storage_path, tmp)) 673 except: 674 pass
675 676 677 @classmethod
678 - def update_state_from_dict(cls, build, upd_dict):
679 """ 680 :param build: 681 :param upd_dict: 682 example: 683 { 684 "builds":[ 685 { 686 "id": 1, 687 "copr_id": 2, 688 "started_on": 139086644000 689 }, 690 { 691 "id": 2, 692 "copr_id": 1, 693 "status": 0, 694 "chroot": "fedora-18-x86_64", 695 "results": "http://server/results/foo/bar/", 696 "ended_on": 139086644000 697 }] 698 } 699 """ 700 log.info("Updating build: {} by: {}".format(build.id, upd_dict)) 701 if "chroot" in upd_dict: 702 # update respective chroot status 703 for build_chroot in build.build_chroots: 704 if build_chroot.name == upd_dict["chroot"]: 705 706 if "status" in upd_dict and build_chroot.status not in BuildsLogic.terminal_states: 707 build_chroot.status = upd_dict["status"] 708 709 if upd_dict.get("status") in BuildsLogic.terminal_states: 710 build_chroot.ended_on = upd_dict.get("ended_on") or time.time() 711 712 if upd_dict.get("status") == StatusEnum("starting"): 713 build_chroot.started_on = upd_dict.get("started_on") or time.time() 714 715 if "last_deferred" in upd_dict: 716 build_chroot.last_deferred = upd_dict["last_deferred"] 717 718 db.session.add(build_chroot) 719 720 for attr in ["results", "built_packages"]: 721 value = upd_dict.get(attr, None) 722 if value: 723 setattr(build, attr, value) 724 725 db.session.add(build)
726 727 @classmethod
728 - def cancel_build(cls, user, build):
729 if not user.can_build_in(build.copr): 730 raise exceptions.InsufficientRightsException( 731 "You are not allowed to cancel this build.") 732 if not build.cancelable: 733 if build.status == StatusEnum("starting"): 734 err_msg = "Cannot cancel build {} in state 'starting'".format(build.id) 735 else: 736 err_msg = "Cannot cancel build {}".format(build.id) 737 raise exceptions.RequestCannotBeExecuted(err_msg) 738 739 if build.status == StatusEnum("running"): # otherwise the build is just in frontend 740 ActionsLogic.send_cancel_build(build) 741 742 build.canceled = True 743 for chroot in build.build_chroots: 744 chroot.status = 2 # canceled 745 if chroot.ended_on is not None: 746 chroot.ended_on = time.time()
747 748 @classmethod
749 - def delete_build(cls, user, build, send_delete_action=True):
750 """ 751 :type user: models.User 752 :type build: models.Build 753 """ 754 if not user.can_edit(build.copr) or build.persistent: 755 raise exceptions.InsufficientRightsException( 756 "You are not allowed to delete build `{}`.".format(build.id)) 757 758 if not build.finished: 759 # from celery.contrib import rdb; rdb.set_trace() 760 raise exceptions.ActionInProgressException( 761 "You can not delete build `{}` which is not finished.".format(build.id), 762 "Unfinished build") 763 764 if send_delete_action: 765 ActionsLogic.send_delete_build(build) 766 767 for build_chroot in build.build_chroots: 768 db.session.delete(build_chroot) 769 db.session.delete(build)
770 771 @classmethod
772 - def mark_as_failed(cls, build_id):
773 """ 774 Marks build as failed on all its non-finished chroots 775 """ 776 build = cls.get(build_id).one() 777 chroots = filter(lambda x: x.status != helpers.StatusEnum("succeeded"), build.build_chroots) 778 for chroot in chroots: 779 chroot.status = helpers.StatusEnum("failed") 780 return build
781 782 @classmethod
783 - def last_modified(cls, copr):
784 """ Get build datetime (as epoch) of last successful build 785 786 :arg copr: object of copr 787 """ 788 builds = cls.get_multiple_by_copr(copr) 789 790 last_build = ( 791 builds.join(models.BuildChroot) 792 .filter((models.BuildChroot.status == helpers.StatusEnum("succeeded")) 793 | (models.BuildChroot.status == helpers.StatusEnum("skipped"))) 794 .filter(models.BuildChroot.ended_on.isnot(None)) 795 .order_by(models.BuildChroot.ended_on.desc()) 796 ).first() 797 if last_build: 798 return last_build.ended_on 799 else: 800 return None
801 802 @classmethod
803 - def filter_is_finished(cls, query, is_finished):
804 # todo: check that ended_on is set correctly for all cases 805 # e.g.: failed dist-git import, cancellation 806 if is_finished: 807 return query.join(models.BuildChroot).filter(models.BuildChroot.ended_on.isnot(None)) 808 else: 809 return query.join(models.BuildChroot).filter(models.BuildChroot.ended_on.is_(None))
810 811 @classmethod
812 - def filter_by_group_name(cls, query, group_name):
813 return query.filter(models.Group.name == group_name)
814
815 816 -class BuildChrootsLogic(object):
817 @classmethod
818 - def get_by_build_id_and_name(cls, build_id, name):
819 mc = MockChrootsLogic.get_from_name(name).one() 820 821 return ( 822 BuildChroot.query 823 .filter(BuildChroot.build_id == build_id) 824 .filter(BuildChroot.mock_chroot_id == mc.id) 825 )
826 827 @classmethod
828 - def get_multiply(cls):
829 query = ( 830 models.BuildChroot.query 831 .join(models.BuildChroot.build) 832 .join(models.BuildChroot.mock_chroot) 833 .join(models.Build.copr) 834 .join(models.Copr.user) 835 .outerjoin(models.Group) 836 ) 837 return query
838 839 @classmethod
840 - def filter_by_build_id(cls, query, build_id):
841 return query.filter(models.Build.id == build_id)
842 843 @classmethod
844 - def filter_by_project_id(cls, query, project_id):
845 return query.filter(models.Copr.id == project_id)
846 847 @classmethod
848 - def filter_by_project_user_name(cls, query, username):
849 return query.filter(models.User.username == username)
850 851 @classmethod
852 - def filter_by_state(cls, query, state):
854 855 @classmethod
856 - def filter_by_group_name(cls, query, group_name):
857 return query.filter(models.Group.name == group_name)
858
859 860 -class BuildsMonitorLogic(object):
861 @classmethod
862 - def get_monitor_data(cls, copr):
863 query = """ 864 SELECT 865 package.id as package_id, 866 package.name AS package_name, 867 build.id AS build_id, 868 build_chroot.status AS build_chroot_status, 869 build.pkg_version AS build_pkg_version, 870 mock_chroot.id AS mock_chroot_id, 871 mock_chroot.os_release AS mock_chroot_os_release, 872 mock_chroot.os_version AS mock_chroot_os_version, 873 mock_chroot.arch AS mock_chroot_arch 874 FROM package 875 JOIN (SELECT 876 MAX(build.id) AS max_build_id_for_chroot, 877 build.package_id AS package_id, 878 build_chroot.mock_chroot_id AS mock_chroot_id 879 FROM build 880 JOIN build_chroot 881 ON build.id = build_chroot.build_id 882 WHERE build.copr_id = {copr_id} 883 AND build_chroot.status != 2 884 GROUP BY build.package_id, 885 build_chroot.mock_chroot_id) AS max_build_ids_for_a_chroot 886 ON package.id = max_build_ids_for_a_chroot.package_id 887 JOIN build 888 ON build.id = max_build_ids_for_a_chroot.max_build_id_for_chroot 889 JOIN build_chroot 890 ON build_chroot.mock_chroot_id = max_build_ids_for_a_chroot.mock_chroot_id 891 AND build_chroot.build_id = max_build_ids_for_a_chroot.max_build_id_for_chroot 892 JOIN mock_chroot 893 ON mock_chroot.id = max_build_ids_for_a_chroot.mock_chroot_id 894 ORDER BY package.name ASC, package.id ASC, mock_chroot.os_release ASC, mock_chroot.os_version ASC, mock_chroot.arch ASC 895 """.format(copr_id=copr.id) 896 rows = db.session.execute(query) 897 return rows
898