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('srpm_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, srpm_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("srpm_link") 378 source_json = json.dumps({"url": srpm_url}) 379 return cls.create_new(user, copr, source_type, source_json, chroot_names, pkgs=srpm_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, 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 "spec": spec}) 415 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
416 417 @classmethod
418 - def create_new_from_pypi(cls, user, copr, pypi_package_name, pypi_package_version, python_versions, 419 chroot_names=None, **build_options):
420 """ 421 :type user: models.User 422 :type copr: models.Copr 423 :type package_name: str 424 :type version: str 425 :type python_versions: List[str] 426 427 :type chroot_names: List[str] 428 429 :rtype: models.Build 430 """ 431 source_type = helpers.BuildSourceEnum("pypi") 432 source_json = json.dumps({"pypi_package_name": pypi_package_name, 433 "pypi_package_version": pypi_package_version, 434 "python_versions": python_versions}) 435 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
436 437 @classmethod
438 - def create_new_from_rubygems(cls, user, copr, gem_name, 439 chroot_names=None, **build_options):
440 """ 441 :type user: models.User 442 :type copr: models.Copr 443 :type gem_name: str 444 :type chroot_names: List[str] 445 :rtype: models.Build 446 """ 447 source_type = helpers.BuildSourceEnum("rubygems") 448 source_json = json.dumps({"gem_name": gem_name}) 449 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
450 451 @classmethod
452 - def create_new_from_distgit(cls, user, copr, clone_url, branch, 453 chroot_names=None, **build_options):
454 """ 455 :type user: models.User 456 :type copr: models.Copr 457 :type clone_url: str 458 :type branch: str 459 :type chroot_names: List[str] 460 :rtype: models.Build 461 """ 462 source_type = helpers.BuildSourceEnum("distgit") 463 source_json = json.dumps({ 464 "clone_url": clone_url, 465 "branch": branch 466 }) 467 return cls.create_new(user, copr, source_type, source_json, chroot_names, **build_options)
468 469 @classmethod
470 - def create_new_from_upload(cls, user, copr, f_uploader, orig_filename, 471 chroot_names=None, **build_options):
472 """ 473 :type user: models.User 474 :type copr: models.Copr 475 :param f_uploader(file_path): function which stores data at the given `file_path` 476 :return: 477 """ 478 tmp = tempfile.mkdtemp(dir=app.config["SRPM_STORAGE_DIR"]) 479 tmp_name = os.path.basename(tmp) 480 filename = secure_filename(orig_filename) 481 file_path = os.path.join(tmp, filename) 482 f_uploader(file_path) 483 484 # make the pkg public 485 pkg_url = "https://{hostname}/tmp/{tmp_dir}/{srpm}".format( 486 hostname=app.config["PUBLIC_COPR_HOSTNAME"], 487 tmp_dir=tmp_name, 488 srpm=filename) 489 490 # create json describing the build source 491 source_type = helpers.BuildSourceEnum("srpm_upload") 492 source_json = json.dumps({"tmp": tmp_name, "pkg": filename}) 493 try: 494 build = cls.create_new(user, copr, source_type, source_json, 495 chroot_names, pkgs=pkg_url, **build_options) 496 except Exception: 497 shutil.rmtree(tmp) # todo: maybe we should delete in some cleanup procedure? 498 raise 499 500 return build
501 502 @classmethod
503 - def create_new(cls, user, copr, source_type, source_json, chroot_names=None, 504 pkgs="", git_hashes=None, skip_import=False, background=False, **build_options):
505 """ 506 :type user: models.User 507 :type copr: models.Copr 508 :type chroot_names: List[str] 509 :type source_type: int value from helpers.BuildSourceEnum 510 :type source_json: str in json format 511 :type pkgs: str 512 :type git_hashes: dict 513 :type skip_import: bool 514 :type background: bool 515 :rtype: models.Build 516 """ 517 if chroot_names is None: 518 chroots = [c for c in copr.active_chroots] 519 else: 520 chroots = [] 521 for chroot in copr.active_chroots: 522 if chroot.name in chroot_names: 523 chroots.append(chroot) 524 525 build = cls.add( 526 user=user, 527 pkgs=pkgs, 528 copr=copr, 529 chroots=chroots, 530 source_type=source_type, 531 source_json=source_json, 532 enable_net=build_options.get("enable_net", copr.build_enable_net), 533 background=background, 534 git_hashes=git_hashes, 535 skip_import=skip_import) 536 537 if user.proven: 538 if "timeout" in build_options: 539 build.timeout = build_options["timeout"] 540 541 return build
542 543 @classmethod
544 - def add(cls, user, pkgs, copr, source_type=None, source_json=None, 545 repos=None, chroots=None, timeout=None, enable_net=True, 546 git_hashes=None, skip_import=False, background=False):
547 if chroots is None: 548 chroots = [] 549 550 coprs_logic.CoprsLogic.raise_if_unfinished_blocking_action( 551 copr, "Can't build while there is an operation in progress: {action}") 552 users_logic.UsersLogic.raise_if_cant_build_in_copr( 553 user, copr, 554 "You don't have permissions to build in this copr.") 555 556 if not repos: 557 repos = copr.repos 558 559 # todo: eliminate pkgs and this check 560 if pkgs and (" " in pkgs or "\n" in pkgs or "\t" in pkgs or pkgs.strip() != pkgs): 561 raise exceptions.MalformedArgumentException("Trying to create a build using src_pkg " 562 "with bad characters. Forgot to split?") 563 564 # just temporary to keep compatibility 565 if not source_type or not source_json: 566 source_type = helpers.BuildSourceEnum("srpm_link") 567 source_json = json.dumps({"url":pkgs}) 568 569 build = models.Build( 570 user=user, 571 pkgs=pkgs, 572 copr=copr, 573 repos=repos, 574 source_type=source_type, 575 source_json=source_json, 576 submitted_on=int(time.time()), 577 enable_net=bool(enable_net), 578 is_background=bool(background), 579 ) 580 581 if timeout: 582 build.timeout = timeout or DEFAULT_BUILD_TIMEOUT 583 584 db.session.add(build) 585 586 # add BuildChroot object for each active (or selected) chroot 587 # this copr is assigned to 588 if not chroots: 589 chroots = copr.active_chroots 590 591 status = helpers.StatusEnum("importing") 592 593 if skip_import: 594 status = StatusEnum("pending") 595 596 for chroot in chroots: 597 git_hash = None 598 if git_hashes: 599 git_hash = git_hashes.get(chroot.name) 600 buildchroot = models.BuildChroot( 601 build=build, 602 status=status, 603 mock_chroot=chroot, 604 git_hash=git_hash) 605 606 db.session.add(buildchroot) 607 608 return build
609 610 @classmethod
611 - def rebuild_package(cls, package):
612 build = models.Build( 613 user=None, 614 pkgs=None, 615 package_id=package.id, 616 copr=package.copr, 617 repos=package.copr.repos, 618 source_type=package.source_type, 619 source_json=package.source_json, 620 submitted_on=int(time.time()), 621 enable_net=package.copr.build_enable_net, 622 timeout=DEFAULT_BUILD_TIMEOUT 623 ) 624 625 db.session.add(build) 626 627 chroots = package.copr.active_chroots 628 629 status = helpers.StatusEnum("importing") 630 631 for chroot in chroots: 632 buildchroot = models.BuildChroot( 633 build=build, 634 status=status, 635 mock_chroot=chroot, 636 git_hash=None 637 ) 638 639 db.session.add(buildchroot) 640 641 return build
642 643 644 terminal_states = {StatusEnum("failed"), StatusEnum("succeeded"), StatusEnum("canceled")} 645 646 @classmethod
647 - def get_buildchroots_by_build_id_and_branch(cls, build_id, branch):
648 """ 649 Returns a list of BuildChroots identified by build_id and dist-git 650 branch name. 651 """ 652 return ( 653 models.BuildChroot.query 654 .join(models.MockChroot) 655 .filter(models.BuildChroot.build_id==build_id) 656 .filter(models.MockChroot.distgit_branch_name==branch) 657 ).all()
658 659 660 @classmethod
661 - def delete_local_srpm(cls, build):
662 """ 663 Deletes the source rpm locally stored for upload (if exists) 664 """ 665 # is it hosted on the copr frontend? 666 if build.source_type == helpers.BuildSourceEnum("srpm_upload"): 667 data = json.loads(build.source_json) 668 tmp = data["tmp"] 669 storage_path = app.config["SRPM_STORAGE_DIR"] 670 try: 671 shutil.rmtree(os.path.join(storage_path, tmp)) 672 except: 673 pass
674 675 676 @classmethod
677 - def update_state_from_dict(cls, build, upd_dict):
678 """ 679 :param build: 680 :param upd_dict: 681 example: 682 { 683 "builds":[ 684 { 685 "id": 1, 686 "copr_id": 2, 687 "started_on": 139086644000 688 }, 689 { 690 "id": 2, 691 "copr_id": 1, 692 "status": 0, 693 "chroot": "fedora-18-x86_64", 694 "results": "http://server/results/foo/bar/", 695 "ended_on": 139086644000 696 }] 697 } 698 """ 699 log.info("Updating build: {} by: {}".format(build.id, upd_dict)) 700 if "chroot" in upd_dict: 701 # update respective chroot status 702 for build_chroot in build.build_chroots: 703 if build_chroot.name == upd_dict["chroot"]: 704 705 if "status" in upd_dict and build_chroot.status not in BuildsLogic.terminal_states: 706 build_chroot.status = upd_dict["status"] 707 708 if upd_dict.get("status") in BuildsLogic.terminal_states: 709 build_chroot.ended_on = upd_dict.get("ended_on") or time.time() 710 711 if upd_dict.get("status") == StatusEnum("starting"): 712 build_chroot.started_on = upd_dict.get("started_on") or time.time() 713 714 if "last_deferred" in upd_dict: 715 build_chroot.last_deferred = upd_dict["last_deferred"] 716 717 db.session.add(build_chroot) 718 719 for attr in ["results", "built_packages"]: 720 value = upd_dict.get(attr, None) 721 if value: 722 setattr(build, attr, value) 723 724 db.session.add(build)
725 726 @classmethod
727 - def cancel_build(cls, user, build):
728 if not user.can_build_in(build.copr): 729 raise exceptions.InsufficientRightsException( 730 "You are not allowed to cancel this build.") 731 if not build.cancelable: 732 if build.status == StatusEnum("starting"): 733 err_msg = "Cannot cancel build {} in state 'starting'".format(build.id) 734 else: 735 err_msg = "Cannot cancel build {}".format(build.id) 736 raise exceptions.RequestCannotBeExecuted(err_msg) 737 738 if build.status == StatusEnum("running"): # otherwise the build is just in frontend 739 ActionsLogic.send_cancel_build(build) 740 741 build.canceled = True 742 for chroot in build.build_chroots: 743 chroot.status = 2 # canceled 744 if chroot.ended_on is not None: 745 chroot.ended_on = time.time()
746 747 @classmethod
748 - def delete_build(cls, user, build, send_delete_action=True):
749 """ 750 :type user: models.User 751 :type build: models.Build 752 """ 753 if not user.can_edit(build.copr) or build.persistent: 754 raise exceptions.InsufficientRightsException( 755 "You are not allowed to delete build `{}`.".format(build.id)) 756 757 if not build.finished: 758 # from celery.contrib import rdb; rdb.set_trace() 759 raise exceptions.ActionInProgressException( 760 "You can not delete build `{}` which is not finished.".format(build.id), 761 "Unfinished build") 762 763 if send_delete_action: 764 ActionsLogic.send_delete_build(build) 765 766 for build_chroot in build.build_chroots: 767 db.session.delete(build_chroot) 768 db.session.delete(build)
769 770 @classmethod
771 - def mark_as_failed(cls, build_id):
772 """ 773 Marks build as failed on all its non-finished chroots 774 """ 775 build = cls.get(build_id).one() 776 chroots = filter(lambda x: x.status != helpers.StatusEnum("succeeded"), build.build_chroots) 777 for chroot in chroots: 778 chroot.status = helpers.StatusEnum("failed") 779 return build
780 781 @classmethod
782 - def last_modified(cls, copr):
783 """ Get build datetime (as epoch) of last successful build 784 785 :arg copr: object of copr 786 """ 787 builds = cls.get_multiple_by_copr(copr) 788 789 last_build = ( 790 builds.join(models.BuildChroot) 791 .filter((models.BuildChroot.status == helpers.StatusEnum("succeeded")) 792 | (models.BuildChroot.status == helpers.StatusEnum("skipped"))) 793 .filter(models.BuildChroot.ended_on.isnot(None)) 794 .order_by(models.BuildChroot.ended_on.desc()) 795 ).first() 796 if last_build: 797 return last_build.ended_on 798 else: 799 return None
800 801 @classmethod
802 - def filter_is_finished(cls, query, is_finished):
803 # todo: check that ended_on is set correctly for all cases 804 # e.g.: failed dist-git import, cancellation 805 if is_finished: 806 return query.join(models.BuildChroot).filter(models.BuildChroot.ended_on.isnot(None)) 807 else: 808 return query.join(models.BuildChroot).filter(models.BuildChroot.ended_on.is_(None))
809 810 @classmethod
811 - def filter_by_group_name(cls, query, group_name):
812 return query.filter(models.Group.name == group_name)
813
814 815 -class BuildChrootsLogic(object):
816 @classmethod
817 - def get_by_build_id_and_name(cls, build_id, name):
818 mc = MockChrootsLogic.get_from_name(name).one() 819 820 return ( 821 BuildChroot.query 822 .filter(BuildChroot.build_id == build_id) 823 .filter(BuildChroot.mock_chroot_id == mc.id) 824 )
825 826 @classmethod
827 - def get_multiply(cls):
828 query = ( 829 models.BuildChroot.query 830 .join(models.BuildChroot.build) 831 .join(models.BuildChroot.mock_chroot) 832 .join(models.Build.copr) 833 .join(models.Copr.user) 834 .outerjoin(models.Group) 835 ) 836 return query
837 838 @classmethod
839 - def filter_by_build_id(cls, query, build_id):
840 return query.filter(models.Build.id == build_id)
841 842 @classmethod
843 - def filter_by_project_id(cls, query, project_id):
844 return query.filter(models.Copr.id == project_id)
845 846 @classmethod
847 - def filter_by_project_user_name(cls, query, username):
848 return query.filter(models.User.username == username)
849 850 @classmethod
851 - def filter_by_state(cls, query, state):
853 854 @classmethod
855 - def filter_by_group_name(cls, query, group_name):
856 return query.filter(models.Group.name == group_name)
857
858 859 -class BuildsMonitorLogic(object):
860 @classmethod
861 - def get_monitor_data(cls, copr):
862 query = """ 863 SELECT 864 package.id as package_id, 865 package.name AS package_name, 866 build.id AS build_id, 867 build_chroot.status AS build_chroot_status, 868 build.pkg_version AS build_pkg_version, 869 mock_chroot.id AS mock_chroot_id, 870 mock_chroot.os_release AS mock_chroot_os_release, 871 mock_chroot.os_version AS mock_chroot_os_version, 872 mock_chroot.arch AS mock_chroot_arch 873 FROM package 874 JOIN (SELECT 875 MAX(build.id) AS max_build_id_for_chroot, 876 build.package_id AS package_id, 877 build_chroot.mock_chroot_id AS mock_chroot_id 878 FROM build 879 JOIN build_chroot 880 ON build.id = build_chroot.build_id 881 WHERE build.copr_id = {copr_id} 882 AND build_chroot.status != 2 883 GROUP BY build.package_id, 884 build_chroot.mock_chroot_id) AS max_build_ids_for_a_chroot 885 ON package.id = max_build_ids_for_a_chroot.package_id 886 JOIN build 887 ON build.id = max_build_ids_for_a_chroot.max_build_id_for_chroot 888 JOIN build_chroot 889 ON build_chroot.mock_chroot_id = max_build_ids_for_a_chroot.mock_chroot_id 890 AND build_chroot.build_id = max_build_ids_for_a_chroot.max_build_id_for_chroot 891 JOIN mock_chroot 892 ON mock_chroot.id = max_build_ids_for_a_chroot.mock_chroot_id 893 ORDER BY package.name ASC, package.id ASC, mock_chroot.os_release ASC, mock_chroot.os_version ASC, mock_chroot.arch ASC 894 """.format(copr_id=copr.id) 895 rows = db.session.execute(query) 896 return rows
897