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