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