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