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