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