github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/job.go (about) 1 package db 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "sort" 9 "strconv" 10 "strings" 11 "time" 12 13 "code.cloudfoundry.org/lager" 14 sq "github.com/Masterminds/squirrel" 15 "github.com/pf-qiu/concourse/v6/atc" 16 "github.com/pf-qiu/concourse/v6/atc/db/lock" 17 "github.com/pf-qiu/concourse/v6/tracing" 18 "github.com/lib/pq" 19 ) 20 21 type InputConfigs []InputConfig 22 23 type InputConfig struct { 24 Name string 25 Trigger bool 26 Passed JobSet 27 UseEveryVersion bool 28 PinnedVersion atc.Version 29 ResourceID int 30 JobID int 31 } 32 33 func (cfgs InputConfigs) String() string { 34 if !tracing.Configured { 35 return "" 36 } 37 38 names := make([]string, len(cfgs)) 39 for i, cfg := range cfgs { 40 names[i] = cfg.Name 41 } 42 43 return strings.Join(names, ",") 44 } 45 46 type InputVersionEmptyError struct { 47 InputName string 48 } 49 50 func (e InputVersionEmptyError) Error() string { 51 return fmt.Sprintf("input '%s' has successfully resolved but contains missing version information", e.InputName) 52 } 53 54 //go:generate counterfeiter . Job 55 56 type Job interface { 57 PipelineRef 58 59 ID() int 60 Name() string 61 Paused() bool 62 FirstLoggedBuildID() int 63 TeamID() int 64 TeamName() string 65 Tags() []string 66 Public() bool 67 ScheduleRequestedTime() time.Time 68 MaxInFlight() int 69 DisableManualTrigger() bool 70 71 Config() (atc.JobConfig, error) 72 Inputs() ([]atc.JobInput, error) 73 Outputs() ([]atc.JobOutput, error) 74 AlgorithmInputs() (InputConfigs, error) 75 76 Reload() (bool, error) 77 78 Pause() error 79 Unpause() error 80 81 ScheduleBuild(Build) (bool, error) 82 CreateBuild() (Build, error) 83 RerunBuild(Build) (Build, error) 84 85 RequestSchedule() error 86 UpdateLastScheduled(time.Time) error 87 88 Builds(page Page) ([]Build, Pagination, error) 89 BuildsWithTime(page Page) ([]Build, Pagination, error) 90 Build(name string) (Build, bool, error) 91 FinishedAndNextBuild() (Build, Build, error) 92 UpdateFirstLoggedBuildID(newFirstLoggedBuildID int) error 93 EnsurePendingBuildExists(context.Context) error 94 GetPendingBuilds() ([]Build, error) 95 96 GetNextBuildInputs() ([]BuildInput, error) 97 GetFullNextBuildInputs() ([]BuildInput, bool, error) 98 SaveNextInputMapping(inputMapping InputMapping, inputsDetermined bool) error 99 100 ClearTaskCache(string, string) (int64, error) 101 102 AcquireSchedulingLock(lager.Logger) (lock.Lock, bool, error) 103 104 SetHasNewInputs(bool) error 105 HasNewInputs() bool 106 } 107 108 var jobsQuery = psql.Select("j.id", "j.name", "j.config", "j.paused", "j.public", "j.first_logged_build_id", "j.pipeline_id", "p.name", "p.instance_vars", "p.team_id", "t.name", "j.nonce", "j.tags", "j.has_new_inputs", "j.schedule_requested", "j.max_in_flight", "j.disable_manual_trigger"). 109 From("jobs j, pipelines p"). 110 LeftJoin("teams t ON p.team_id = t.id"). 111 Where(sq.Expr("j.pipeline_id = p.id")) 112 113 type FirstLoggedBuildIDDecreasedError struct { 114 Job string 115 OldID int 116 NewID int 117 } 118 119 func (e FirstLoggedBuildIDDecreasedError) Error() string { 120 return fmt.Sprintf("first logged build id for job '%s' decreased from %d to %d", e.Job, e.OldID, e.NewID) 121 } 122 123 type job struct { 124 pipelineRef 125 126 id int 127 name string 128 paused bool 129 public bool 130 firstLoggedBuildID int 131 teamID int 132 teamName string 133 tags []string 134 hasNewInputs bool 135 scheduleRequestedTime time.Time 136 maxInFlight int 137 disableManualTrigger bool 138 139 config *atc.JobConfig 140 rawConfig *string 141 nonce *string 142 } 143 144 func newEmptyJob(conn Conn, lockFactory lock.LockFactory) *job { 145 return &job{pipelineRef: pipelineRef{conn: conn, lockFactory: lockFactory}} 146 } 147 148 func (j *job) SetHasNewInputs(hasNewInputs bool) error { 149 result, err := psql.Update("jobs"). 150 Set("has_new_inputs", hasNewInputs). 151 Where(sq.Eq{"id": j.id}). 152 RunWith(j.conn). 153 Exec() 154 if err != nil { 155 return err 156 } 157 158 rowsAffected, err := result.RowsAffected() 159 if err != nil { 160 return err 161 } 162 163 if rowsAffected != 1 { 164 return NonOneRowAffectedError{rowsAffected} 165 } 166 167 return nil 168 } 169 170 type Jobs []Job 171 172 func (jobs Jobs) Configs() (atc.JobConfigs, error) { 173 var configs atc.JobConfigs 174 175 for _, j := range jobs { 176 config, err := j.Config() 177 if err != nil { 178 return nil, err 179 } 180 181 configs = append(configs, config) 182 } 183 184 return configs, nil 185 } 186 187 func (j *job) ID() int { return j.id } 188 func (j *job) Name() string { return j.name } 189 func (j *job) Paused() bool { return j.paused } 190 func (j *job) Public() bool { return j.public } 191 func (j *job) FirstLoggedBuildID() int { return j.firstLoggedBuildID } 192 func (j *job) TeamID() int { return j.teamID } 193 func (j *job) TeamName() string { return j.teamName } 194 func (j *job) Tags() []string { return j.tags } 195 func (j *job) HasNewInputs() bool { return j.hasNewInputs } 196 func (j *job) ScheduleRequestedTime() time.Time { return j.scheduleRequestedTime } 197 func (j *job) MaxInFlight() int { return j.maxInFlight } 198 func (j *job) DisableManualTrigger() bool { return j.disableManualTrigger } 199 200 func (j *job) Config() (atc.JobConfig, error) { 201 if j.config != nil { 202 return *j.config, nil 203 } 204 205 es := j.conn.EncryptionStrategy() 206 207 if j.rawConfig == nil { 208 return atc.JobConfig{}, nil 209 } 210 211 decryptedConfig, err := es.Decrypt(*j.rawConfig, j.nonce) 212 if err != nil { 213 return atc.JobConfig{}, err 214 } 215 216 var config atc.JobConfig 217 err = json.Unmarshal(decryptedConfig, &config) 218 if err != nil { 219 return atc.JobConfig{}, err 220 } 221 222 j.config = &config 223 return config, nil 224 } 225 226 func (j *job) AlgorithmInputs() (InputConfigs, error) { 227 rows, err := psql.Select("ji.name", "ji.resource_id", "array_agg(ji.passed_job_id)", "ji.version", "rp.version", "ji.trigger"). 228 From("job_inputs ji"). 229 LeftJoin("resource_pins rp ON rp.resource_id = ji.resource_id"). 230 Where(sq.Eq{ 231 "ji.job_id": j.id, 232 }). 233 GroupBy("ji.name, ji.job_id, ji.resource_id, ji.version, rp.version, ji.trigger"). 234 RunWith(j.conn). 235 Query() 236 if err != nil { 237 return nil, err 238 } 239 240 var inputs InputConfigs 241 for rows.Next() { 242 var passedJobs []sql.NullInt64 243 var configVersionString, pinnedVersionString sql.NullString 244 var inputName string 245 var resourceID int 246 var trigger bool 247 248 err = rows.Scan(&inputName, &resourceID, pq.Array(&passedJobs), &configVersionString, &pinnedVersionString, &trigger) 249 if err != nil { 250 return nil, err 251 } 252 253 inputConfig := InputConfig{ 254 Name: inputName, 255 ResourceID: resourceID, 256 JobID: j.id, 257 Trigger: trigger, 258 } 259 260 if pinnedVersionString.Valid { 261 err = json.Unmarshal([]byte(pinnedVersionString.String), &inputConfig.PinnedVersion) 262 if err != nil { 263 return nil, err 264 } 265 } 266 267 var version *atc.VersionConfig 268 if configVersionString.Valid { 269 version = &atc.VersionConfig{} 270 err = version.UnmarshalJSON([]byte(configVersionString.String)) 271 if err != nil { 272 return nil, err 273 } 274 275 inputConfig.UseEveryVersion = version.Every 276 277 if version.Pinned != nil { 278 inputConfig.PinnedVersion = version.Pinned 279 } 280 } 281 282 passed := make(JobSet) 283 for _, s := range passedJobs { 284 if s.Valid { 285 passed[int(s.Int64)] = true 286 } 287 } 288 289 if len(passed) > 0 { 290 inputConfig.Passed = passed 291 } 292 293 inputs = append(inputs, inputConfig) 294 } 295 296 return inputs, nil 297 } 298 299 func (j *job) Inputs() ([]atc.JobInput, error) { 300 rows, err := psql.Select("ji.name", "r.name", "array_agg(p.name ORDER BY p.id)", "ji.trigger", "ji.version"). 301 From("job_inputs ji"). 302 Join("resources r ON r.id = ji.resource_id"). 303 LeftJoin("jobs p ON p.id = ji.passed_job_id"). 304 Where(sq.Eq{ 305 "ji.job_id": j.id, 306 }). 307 GroupBy("ji.name, ji.job_id, r.name, ji.trigger, ji.version"). 308 RunWith(j.conn). 309 Query() 310 if err != nil { 311 return nil, err 312 } 313 314 var inputs []atc.JobInput 315 for rows.Next() { 316 var passedString []sql.NullString 317 var versionString sql.NullString 318 var inputName, resourceName string 319 var trigger bool 320 321 err = rows.Scan(&inputName, &resourceName, pq.Array(&passedString), &trigger, &versionString) 322 if err != nil { 323 return nil, err 324 } 325 326 var version *atc.VersionConfig 327 if versionString.Valid { 328 version = &atc.VersionConfig{} 329 err = version.UnmarshalJSON([]byte(versionString.String)) 330 if err != nil { 331 return nil, err 332 } 333 } 334 335 var passed []string 336 for _, s := range passedString { 337 if s.Valid { 338 passed = append(passed, s.String) 339 } 340 } 341 342 inputs = append(inputs, atc.JobInput{ 343 Name: inputName, 344 Resource: resourceName, 345 Trigger: trigger, 346 Version: version, 347 Passed: passed, 348 }) 349 } 350 351 sort.Slice(inputs, func(p, q int) bool { 352 return inputs[p].Name < inputs[q].Name 353 }) 354 355 return inputs, nil 356 } 357 358 func (j *job) Outputs() ([]atc.JobOutput, error) { 359 rows, err := psql.Select("jo.name", "r.name"). 360 From("job_outputs jo"). 361 Join("resources r ON r.id = jo.resource_id"). 362 Where(sq.Eq{ 363 "jo.job_id": j.id, 364 }). 365 RunWith(j.conn). 366 Query() 367 if err != nil { 368 return nil, err 369 } 370 371 var outputs []atc.JobOutput 372 for rows.Next() { 373 var output atc.JobOutput 374 err = rows.Scan(&output.Name, &output.Resource) 375 if err != nil { 376 return nil, err 377 } 378 379 outputs = append(outputs, output) 380 } 381 382 sort.Slice(outputs, func(p, q int) bool { 383 return outputs[p].Name < outputs[q].Name 384 }) 385 386 return outputs, nil 387 } 388 389 func (j *job) Reload() (bool, error) { 390 row := jobsQuery.Where(sq.Eq{"j.id": j.id}). 391 RunWith(j.conn). 392 QueryRow() 393 394 err := scanJob(j, row) 395 if err != nil { 396 if err == sql.ErrNoRows { 397 return false, nil 398 } 399 return false, err 400 } 401 402 return true, nil 403 } 404 405 func (j *job) Pause() error { 406 return j.updatePausedJob(true) 407 } 408 409 func (j *job) Unpause() error { 410 return j.updatePausedJob(false) 411 } 412 413 func (j *job) FinishedAndNextBuild() (Build, Build, error) { 414 tx, err := j.conn.Begin() 415 if err != nil { 416 return nil, nil, err 417 } 418 419 defer Rollback(tx) 420 421 next, err := j.nextBuild(tx) 422 if err != nil { 423 return nil, nil, err 424 } 425 426 finished, err := j.finishedBuild(tx) 427 if err != nil { 428 return nil, nil, err 429 } 430 431 // query next build again if the build state changed between the two queries 432 if next != nil && finished != nil && next.ID() == finished.ID() { 433 next = nil 434 435 next, err = j.nextBuild(tx) 436 if err != nil { 437 return nil, nil, err 438 } 439 } 440 441 err = tx.Commit() 442 if err != nil { 443 return nil, nil, err 444 } 445 446 return finished, next, nil 447 } 448 449 func (j *job) UpdateFirstLoggedBuildID(newFirstLoggedBuildID int) error { 450 if j.firstLoggedBuildID > newFirstLoggedBuildID { 451 return FirstLoggedBuildIDDecreasedError{ 452 Job: j.name, 453 OldID: j.firstLoggedBuildID, 454 NewID: newFirstLoggedBuildID, 455 } 456 } 457 458 result, err := psql.Update("jobs"). 459 Set("first_logged_build_id", newFirstLoggedBuildID). 460 Where(sq.Eq{"id": j.id}). 461 RunWith(j.conn). 462 Exec() 463 if err != nil { 464 return err 465 } 466 467 rowsAffected, err := result.RowsAffected() 468 if err != nil { 469 return err 470 } 471 472 if rowsAffected != 1 { 473 return NonOneRowAffectedError{rowsAffected} 474 } 475 476 return nil 477 } 478 479 func (j *job) BuildsWithTime(page Page) ([]Build, Pagination, error) { 480 newBuildsQuery := buildsQuery.Where(sq.Eq{"j.id": j.id}) 481 newMinMaxIdQuery := minMaxIdQuery. 482 Join("jobs j ON b.job_id = j.id"). 483 Where(sq.Eq{ 484 "j.name": j.name, 485 "j.pipeline_id": j.pipelineID, 486 }) 487 return getBuildsWithDates(newBuildsQuery, newMinMaxIdQuery, page, j.conn, j.lockFactory) 488 } 489 490 func (j *job) Builds(page Page) ([]Build, Pagination, error) { 491 newBuildsQuery := buildsQuery.Where(sq.Eq{"j.id": j.id}) 492 newMinMaxIdQuery := minMaxIdQuery. 493 Join("jobs j ON b.job_id = j.id"). 494 Where(sq.Eq{ 495 "j.name": j.name, 496 "j.pipeline_id": j.pipelineID, 497 }) 498 499 return getBuildsWithPagination(newBuildsQuery, newMinMaxIdQuery, page, j.conn, j.lockFactory) 500 } 501 502 func (j *job) Build(name string) (Build, bool, error) { 503 var query sq.SelectBuilder 504 505 if name == "latest" { 506 query = buildsQuery. 507 Where(sq.Eq{"b.job_id": j.id}). 508 OrderBy("b.id DESC"). 509 Limit(1) 510 } else { 511 query = buildsQuery.Where(sq.Eq{ 512 "b.job_id": j.id, 513 "b.name": name, 514 }) 515 } 516 517 row := query.RunWith(j.conn).QueryRow() 518 519 build := newEmptyBuild(j.conn, j.lockFactory) 520 521 err := scanBuild(build, row, j.conn.EncryptionStrategy()) 522 if err != nil { 523 if err == sql.ErrNoRows { 524 return nil, false, nil 525 } 526 return nil, false, err 527 } 528 529 return build, true, nil 530 } 531 532 func (j *job) ScheduleBuild(build Build) (bool, error) { 533 if build.IsScheduled() { 534 return true, nil 535 } 536 537 tx, err := j.conn.Begin() 538 if err != nil { 539 return false, err 540 } 541 542 defer tx.Rollback() 543 544 paused, err := j.isPipelineOrJobPaused(tx) 545 if err != nil { 546 return false, err 547 } 548 549 if paused { 550 return false, nil 551 } 552 553 reached, err := j.isMaxInFlightReached(tx, build.ID()) 554 if err != nil { 555 return false, err 556 } 557 558 result, err := psql.Update("jobs"). 559 Set("max_in_flight_reached", reached). 560 Where(sq.Eq{ 561 "id": j.id, 562 }). 563 RunWith(tx). 564 Exec() 565 if err != nil { 566 return false, err 567 } 568 569 rowsAffected, err := result.RowsAffected() 570 if err != nil { 571 return false, err 572 } 573 574 if rowsAffected != 1 { 575 return false, NonOneRowAffectedError{rowsAffected} 576 } 577 578 var scheduled bool 579 if !reached { 580 result, err = psql.Update("builds"). 581 Set("scheduled", true). 582 Where(sq.Eq{"id": build.ID()}). 583 RunWith(tx). 584 Exec() 585 if err != nil { 586 return false, err 587 } 588 589 rowsAffected, err := result.RowsAffected() 590 if err != nil { 591 return false, err 592 } 593 594 if rowsAffected != 1 { 595 return false, NonOneRowAffectedError{rowsAffected} 596 } 597 598 scheduled = true 599 } 600 601 err = tx.Commit() 602 if err != nil { 603 return false, err 604 } 605 606 return scheduled, nil 607 } 608 609 func (j *job) GetFullNextBuildInputs() ([]BuildInput, bool, error) { 610 tx, err := j.conn.Begin() 611 if err != nil { 612 return nil, false, err 613 } 614 615 defer tx.Rollback() 616 617 var inputsDetermined bool 618 err = psql.Select("inputs_determined"). 619 From("jobs"). 620 Where(sq.Eq{ 621 "id": j.id, 622 }). 623 RunWith(tx). 624 QueryRow(). 625 Scan(&inputsDetermined) 626 if err != nil { 627 return nil, false, err 628 } 629 630 if !inputsDetermined { 631 return nil, false, nil 632 } 633 634 buildInputs, err := j.getNextBuildInputs(tx) 635 if err != nil { 636 return nil, false, err 637 } 638 639 err = tx.Commit() 640 if err != nil { 641 return nil, false, err 642 } 643 644 return buildInputs, true, nil 645 } 646 647 func (j *job) GetNextBuildInputs() ([]BuildInput, error) { 648 tx, err := j.conn.Begin() 649 if err != nil { 650 return nil, err 651 } 652 653 defer tx.Rollback() 654 655 buildInputs, err := j.getNextBuildInputs(tx) 656 if err != nil { 657 return nil, err 658 } 659 660 err = tx.Commit() 661 if err != nil { 662 return nil, err 663 } 664 665 return buildInputs, nil 666 } 667 668 func (j *job) EnsurePendingBuildExists(ctx context.Context) error { 669 defer tracing.FromContext(ctx).End() 670 spanContextJSON, err := json.Marshal(NewSpanContext(ctx)) 671 if err != nil { 672 return err 673 } 674 675 tx, err := j.conn.Begin() 676 if err != nil { 677 return err 678 } 679 680 defer Rollback(tx) 681 682 buildName, err := j.getNewBuildName(tx) 683 if err != nil { 684 return err 685 } 686 687 rows, err := tx.Query(` 688 INSERT INTO builds (name, job_id, pipeline_id, team_id, status, needs_v6_migration, span_context) 689 SELECT $1, $2, $3, $4, 'pending', false, $5 690 WHERE NOT EXISTS 691 (SELECT id FROM builds WHERE job_id = $2 AND status = 'pending') 692 RETURNING id 693 `, buildName, j.id, j.pipelineID, j.teamID, string(spanContextJSON)) 694 if err != nil { 695 return err 696 } 697 698 defer Close(rows) 699 700 if rows.Next() { 701 var buildID int 702 err := rows.Scan(&buildID) 703 if err != nil { 704 return err 705 } 706 707 err = rows.Close() 708 if err != nil { 709 return err 710 } 711 712 err = createBuildEventSeq(tx, buildID) 713 if err != nil { 714 return err 715 } 716 717 latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id) 718 if err != nil { 719 return err 720 } 721 722 err = updateNextBuildForJob(tx, j.id, latestNonRerunID) 723 if err != nil { 724 return err 725 } 726 727 return tx.Commit() 728 } 729 730 return nil 731 } 732 733 func (j *job) GetPendingBuilds() ([]Build, error) { 734 builds := []Build{} 735 736 row := jobsQuery.Where(sq.Eq{ 737 "j.name": j.name, 738 "j.active": true, 739 "j.pipeline_id": j.pipelineID, 740 }).RunWith(j.conn).QueryRow() 741 742 job := newEmptyJob(j.conn, j.lockFactory) 743 err := scanJob(job, row) 744 if err != nil { 745 return nil, err 746 } 747 748 rows, err := buildsQuery. 749 Where(sq.Eq{ 750 "b.job_id": j.id, 751 "b.status": BuildStatusPending, 752 }). 753 OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC"). 754 RunWith(j.conn). 755 Query() 756 if err != nil { 757 return nil, err 758 } 759 760 defer Close(rows) 761 762 for rows.Next() { 763 build := newEmptyBuild(j.conn, j.lockFactory) 764 err = scanBuild(build, rows, j.conn.EncryptionStrategy()) 765 if err != nil { 766 return nil, err 767 } 768 769 builds = append(builds, build) 770 } 771 772 return builds, nil 773 } 774 775 func (j *job) CreateBuild() (Build, error) { 776 tx, err := j.conn.Begin() 777 if err != nil { 778 return nil, err 779 } 780 781 defer Rollback(tx) 782 783 buildName, err := j.getNewBuildName(tx) 784 if err != nil { 785 return nil, err 786 } 787 788 build := newEmptyBuild(j.conn, j.lockFactory) 789 err = createBuild(tx, build, map[string]interface{}{ 790 "name": buildName, 791 "job_id": j.id, 792 "pipeline_id": j.pipelineID, 793 "team_id": j.teamID, 794 "status": BuildStatusPending, 795 "manually_triggered": true, 796 }) 797 if err != nil { 798 return nil, err 799 } 800 801 latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id) 802 if err != nil { 803 return nil, err 804 } 805 806 err = updateNextBuildForJob(tx, j.id, latestNonRerunID) 807 if err != nil { 808 return nil, err 809 } 810 811 err = requestSchedule(tx, j.id) 812 if err != nil { 813 return nil, err 814 } 815 816 err = tx.Commit() 817 if err != nil { 818 return nil, err 819 } 820 821 return build, nil 822 } 823 824 func (j *job) RerunBuild(buildToRerun Build) (Build, error) { 825 for { 826 rerunBuild, err := j.tryRerunBuild(buildToRerun) 827 if err != nil { 828 if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqUniqueViolationErrCode { 829 continue 830 } 831 832 return nil, err 833 } 834 835 return rerunBuild, nil 836 } 837 } 838 839 func (j *job) tryRerunBuild(buildToRerun Build) (Build, error) { 840 tx, err := j.conn.Begin() 841 if err != nil { 842 return nil, err 843 } 844 845 defer Rollback(tx) 846 847 buildToRerunID := buildToRerun.ID() 848 if buildToRerun.RerunOf() != 0 { 849 buildToRerunID = buildToRerun.RerunOf() 850 } 851 852 rerunBuildName, rerunNumber, err := j.getNewRerunBuildName(tx, buildToRerunID) 853 if err != nil { 854 return nil, err 855 } 856 857 rerunBuild := newEmptyBuild(j.conn, j.lockFactory) 858 err = createBuild(tx, rerunBuild, map[string]interface{}{ 859 "name": rerunBuildName, 860 "job_id": j.id, 861 "pipeline_id": j.pipelineID, 862 "team_id": j.teamID, 863 "status": BuildStatusPending, 864 "rerun_of": buildToRerunID, 865 "rerun_number": rerunNumber, 866 }) 867 if err != nil { 868 return nil, err 869 } 870 871 latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id) 872 if err != nil { 873 return nil, err 874 } 875 876 err = updateNextBuildForJob(tx, j.id, latestNonRerunID) 877 if err != nil { 878 return nil, err 879 } 880 881 err = requestSchedule(tx, j.id) 882 if err != nil { 883 return nil, err 884 } 885 886 err = tx.Commit() 887 if err != nil { 888 return nil, err 889 } 890 891 return rerunBuild, nil 892 } 893 894 func (j *job) ClearTaskCache(stepName string, cachePath string) (int64, error) { 895 tx, err := j.conn.Begin() 896 if err != nil { 897 return 0, err 898 } 899 900 defer Rollback(tx) 901 902 var sqlBuilder sq.DeleteBuilder = psql.Delete("task_caches"). 903 Where(sq.Eq{ 904 "job_id": j.id, 905 "step_name": stepName, 906 }) 907 908 if len(cachePath) > 0 { 909 sqlBuilder = sqlBuilder.Where(sq.Eq{"path": cachePath}) 910 } 911 912 sqlResult, err := sqlBuilder. 913 RunWith(tx). 914 Exec() 915 916 if err != nil { 917 return 0, err 918 } 919 920 rowsDeleted, err := sqlResult.RowsAffected() 921 922 if err != nil { 923 return 0, err 924 } 925 926 return rowsDeleted, tx.Commit() 927 } 928 929 func (j *job) AcquireSchedulingLock(logger lager.Logger) (lock.Lock, bool, error) { 930 return j.lockFactory.Acquire( 931 logger.Session("lock", lager.Data{ 932 "job": j.name, 933 "pipeline": j.pipelineName, 934 }), 935 lock.NewJobSchedulingLockID(j.id), 936 ) 937 } 938 939 func (j *job) isMaxInFlightReached(tx Tx, buildID int) (bool, error) { 940 if j.maxInFlight == 0 { 941 return false, nil 942 } 943 944 serialGroups, err := j.getSerialGroups(tx) 945 if err != nil { 946 return false, err 947 } 948 949 builds, err := j.getRunningBuildsBySerialGroup(tx, serialGroups) 950 if err != nil { 951 return false, err 952 } 953 954 if len(builds) >= j.maxInFlight { 955 return true, nil 956 } 957 958 nextMostPendingBuild, found, err := j.getNextPendingBuildBySerialGroup(tx, serialGroups) 959 if err != nil { 960 return false, err 961 } 962 963 if !found { 964 return true, nil 965 } 966 967 if nextMostPendingBuild.ID() != buildID { 968 return true, nil 969 } 970 971 return false, nil 972 } 973 974 func (j *job) getSerialGroups(tx Tx) ([]string, error) { 975 rows, err := psql.Select("serial_group"). 976 From("jobs_serial_groups"). 977 Where(sq.Eq{ 978 "job_id": j.id, 979 }). 980 RunWith(tx). 981 Query() 982 if err != nil { 983 return nil, err 984 } 985 986 var serialGroups []string 987 for rows.Next() { 988 var serialGroup string 989 err = rows.Scan(&serialGroup) 990 if err != nil { 991 return nil, err 992 } 993 994 serialGroups = append(serialGroups, serialGroup) 995 } 996 997 return serialGroups, nil 998 } 999 1000 func (j *job) RequestSchedule() error { 1001 tx, err := j.conn.Begin() 1002 if err != nil { 1003 return err 1004 } 1005 1006 defer tx.Rollback() 1007 1008 err = requestSchedule(tx, j.id) 1009 if err != nil { 1010 return err 1011 } 1012 1013 return tx.Commit() 1014 } 1015 1016 func (j *job) UpdateLastScheduled(requestedTime time.Time) error { 1017 _, err := psql.Update("jobs"). 1018 Set("last_scheduled", requestedTime). 1019 Where(sq.Eq{ 1020 "id": j.id, 1021 }). 1022 RunWith(j.conn). 1023 Exec() 1024 1025 return err 1026 } 1027 1028 func (j *job) getRunningBuildsBySerialGroup(tx Tx, serialGroups []string) ([]Build, error) { 1029 rows, err := buildsQuery.Options(`DISTINCT ON (b.id)`). 1030 Join(`jobs_serial_groups jsg ON j.id = jsg.job_id`). 1031 Where(sq.Eq{ 1032 "jsg.serial_group": serialGroups, 1033 "j.pipeline_id": j.pipelineID, 1034 }). 1035 Where(sq.Eq{"b.completed": false, "b.scheduled": true}). 1036 RunWith(tx). 1037 Query() 1038 if err != nil { 1039 return nil, err 1040 } 1041 1042 defer Close(rows) 1043 1044 bs := []Build{} 1045 1046 for rows.Next() { 1047 build := newEmptyBuild(j.conn, j.lockFactory) 1048 err = scanBuild(build, rows, j.conn.EncryptionStrategy()) 1049 if err != nil { 1050 return nil, err 1051 } 1052 1053 bs = append(bs, build) 1054 } 1055 1056 return bs, nil 1057 } 1058 1059 func (j *job) getNextPendingBuildBySerialGroup(tx Tx, serialGroups []string) (Build, bool, error) { 1060 subQuery, params, err := buildsQuery.Options(`DISTINCT ON (b.id)`). 1061 Join(`jobs_serial_groups jsg ON j.id = jsg.job_id`). 1062 Where(sq.Eq{ 1063 "jsg.serial_group": serialGroups, 1064 "b.status": BuildStatusPending, 1065 "j.paused": false, 1066 "j.inputs_determined": true, 1067 "j.pipeline_id": j.pipelineID}). 1068 ToSql() 1069 if err != nil { 1070 return nil, false, err 1071 } 1072 1073 row := tx.QueryRow(` 1074 SELECT * FROM (`+subQuery+`) j 1075 ORDER BY COALESCE(rerun_of, id) ASC, id ASC 1076 LIMIT 1`, params...) 1077 1078 build := newEmptyBuild(j.conn, j.lockFactory) 1079 err = scanBuild(build, row, j.conn.EncryptionStrategy()) 1080 if err != nil { 1081 if err == sql.ErrNoRows { 1082 return nil, false, nil 1083 } 1084 return nil, false, err 1085 } 1086 1087 return build, true, nil 1088 } 1089 1090 func (j *job) updatePausedJob(pause bool) error { 1091 result, err := psql.Update("jobs"). 1092 Set("paused", pause). 1093 Where(sq.Eq{"id": j.id}). 1094 RunWith(j.conn). 1095 Exec() 1096 if err != nil { 1097 return err 1098 } 1099 1100 rowsAffected, err := result.RowsAffected() 1101 if err != nil { 1102 return err 1103 } 1104 1105 if rowsAffected != 1 { 1106 return NonOneRowAffectedError{rowsAffected} 1107 } 1108 1109 if !pause { 1110 err = j.RequestSchedule() 1111 if err != nil { 1112 return err 1113 } 1114 } 1115 1116 return nil 1117 } 1118 1119 func (j *job) getNewBuildName(tx Tx) (string, error) { 1120 var buildName string 1121 err := psql.Update("jobs"). 1122 Set("build_number_seq", sq.Expr("build_number_seq + 1")). 1123 Where(sq.Eq{ 1124 "name": j.name, 1125 "pipeline_id": j.pipelineID, 1126 }). 1127 Suffix("RETURNING build_number_seq"). 1128 RunWith(tx). 1129 QueryRow(). 1130 Scan(&buildName) 1131 1132 return buildName, err 1133 } 1134 1135 func (j *job) SaveNextInputMapping(inputMapping InputMapping, inputsDetermined bool) error { 1136 tx, err := j.conn.Begin() 1137 if err != nil { 1138 return err 1139 } 1140 1141 defer Rollback(tx) 1142 1143 _, err = psql.Update("jobs"). 1144 Set("inputs_determined", inputsDetermined). 1145 Where(sq.Eq{"id": j.id}). 1146 RunWith(tx). 1147 Exec() 1148 if err != nil { 1149 return err 1150 } 1151 1152 _, err = psql.Delete("next_build_inputs"). 1153 Where(sq.Eq{"job_id": j.id}). 1154 RunWith(tx).Exec() 1155 if err != nil { 1156 return err 1157 } 1158 1159 builder := psql.Insert("next_build_inputs"). 1160 Columns("input_name", "job_id", "version_md5", "resource_id", "first_occurrence", "resolve_error") 1161 1162 for inputName, inputResult := range inputMapping { 1163 var resolveError sql.NullString 1164 var firstOccurrence sql.NullBool 1165 var versionMD5 sql.NullString 1166 var resourceID sql.NullInt64 1167 1168 if inputResult.ResolveError != "" { 1169 resolveError = sql.NullString{String: string(inputResult.ResolveError), Valid: true} 1170 } else { 1171 if inputResult.Input == nil { 1172 return InputVersionEmptyError{inputName} 1173 } 1174 1175 firstOccurrence = sql.NullBool{Bool: inputResult.Input.FirstOccurrence, Valid: true} 1176 resourceID = sql.NullInt64{Int64: int64(inputResult.Input.ResourceID), Valid: true} 1177 versionMD5 = sql.NullString{String: string(inputResult.Input.Version), Valid: true} 1178 } 1179 1180 builder = builder.Values(inputName, j.id, versionMD5, resourceID, firstOccurrence, resolveError) 1181 } 1182 1183 if len(inputMapping) != 0 { 1184 _, err = builder.RunWith(tx).Exec() 1185 if err != nil { 1186 return err 1187 } 1188 } 1189 1190 _, err = psql.Delete("next_build_pipes"). 1191 Where(sq.Eq{"to_job_id": j.id}). 1192 RunWith(tx).Exec() 1193 if err != nil { 1194 return err 1195 } 1196 1197 pipesBuilder := psql.Insert("next_build_pipes"). 1198 Columns("to_job_id", "from_build_id") 1199 1200 insertPipes := false 1201 for _, inputVersion := range inputMapping { 1202 for _, buildID := range inputVersion.PassedBuildIDs { 1203 pipesBuilder = pipesBuilder.Values(j.ID(), buildID) 1204 insertPipes = true 1205 } 1206 } 1207 1208 if insertPipes { 1209 _, err = pipesBuilder.Suffix("ON CONFLICT DO NOTHING").RunWith(tx).Exec() 1210 if err != nil { 1211 return err 1212 } 1213 } 1214 1215 return tx.Commit() 1216 } 1217 1218 func (j *job) nextBuild(tx Tx) (Build, error) { 1219 var next Build 1220 1221 row := buildsQuery. 1222 Where(sq.Eq{"j.id": j.id}). 1223 Where(sq.Expr("b.id = j.next_build_id")). 1224 RunWith(tx). 1225 QueryRow() 1226 1227 nextBuild := newEmptyBuild(j.conn, j.lockFactory) 1228 err := scanBuild(nextBuild, row, j.conn.EncryptionStrategy()) 1229 if err == nil { 1230 next = nextBuild 1231 } else if err != sql.ErrNoRows { 1232 return nil, err 1233 } 1234 1235 return next, nil 1236 } 1237 1238 func (j *job) finishedBuild(tx Tx) (Build, error) { 1239 var finished Build 1240 1241 row := buildsQuery. 1242 Where(sq.Eq{"j.id": j.id}). 1243 Where(sq.Expr("b.id = j.latest_completed_build_id")). 1244 RunWith(tx). 1245 QueryRow() 1246 1247 finishedBuild := newEmptyBuild(j.conn, j.lockFactory) 1248 err := scanBuild(finishedBuild, row, j.conn.EncryptionStrategy()) 1249 if err == nil { 1250 finished = finishedBuild 1251 } else if err != sql.ErrNoRows { 1252 return nil, err 1253 } 1254 1255 return finished, nil 1256 } 1257 1258 func (j *job) getNewRerunBuildName(tx Tx, buildID int) (string, int, error) { 1259 var rerunNum int 1260 var buildName string 1261 err := psql.Select("b.name", "( SELECT COUNT(id) FROM builds WHERE rerun_of = b.id )"). 1262 From("builds b"). 1263 Where(sq.Eq{ 1264 "b.id": buildID, 1265 }). 1266 RunWith(tx). 1267 QueryRow(). 1268 Scan(&buildName, &rerunNum) 1269 if err != nil { 1270 return "", 0, err 1271 } 1272 1273 // increment the rerun number 1274 rerunNum++ 1275 1276 return buildName + "." + strconv.Itoa(rerunNum), rerunNum, err 1277 } 1278 1279 func (j *job) getNextBuildInputs(tx Tx) ([]BuildInput, error) { 1280 rows, err := psql.Select("i.input_name, i.first_occurrence, i.resource_id, v.version, i.resolve_error, v.span_context"). 1281 From("next_build_inputs i"). 1282 LeftJoin("resources r ON r.id = i.resource_id"). 1283 LeftJoin("resource_config_versions v ON v.version_md5 = i.version_md5 AND r.resource_config_scope_id = v.resource_config_scope_id"). 1284 Where(sq.Eq{ 1285 "i.job_id": j.id, 1286 }). 1287 RunWith(tx). 1288 Query() 1289 if err != nil { 1290 return nil, err 1291 } 1292 1293 buildInputs := []BuildInput{} 1294 for rows.Next() { 1295 var ( 1296 inputName string 1297 firstOcc sql.NullBool 1298 versionBlob sql.NullString 1299 resID sql.NullString 1300 resolveErr sql.NullString 1301 spanContextJSON sql.NullString 1302 ) 1303 1304 err := rows.Scan(&inputName, &firstOcc, &resID, &versionBlob, &resolveErr, &spanContextJSON) 1305 if err != nil { 1306 return nil, err 1307 } 1308 1309 var version atc.Version 1310 if versionBlob.Valid { 1311 err = json.Unmarshal([]byte(versionBlob.String), &version) 1312 if err != nil { 1313 return nil, err 1314 } 1315 } 1316 1317 var firstOccurrence bool 1318 if firstOcc.Valid { 1319 firstOccurrence = firstOcc.Bool 1320 } 1321 1322 var resourceID int 1323 if resID.Valid { 1324 resourceID, err = strconv.Atoi(resID.String) 1325 if err != nil { 1326 return nil, err 1327 } 1328 } 1329 1330 var resolveError string 1331 if resolveErr.Valid { 1332 resolveError = resolveErr.String 1333 } 1334 1335 var spanContext SpanContext 1336 if spanContextJSON.Valid { 1337 err = json.Unmarshal([]byte(spanContextJSON.String), &spanContext) 1338 if err != nil { 1339 return nil, err 1340 } 1341 } 1342 1343 buildInputs = append(buildInputs, BuildInput{ 1344 Name: inputName, 1345 ResourceID: resourceID, 1346 Version: version, 1347 FirstOccurrence: firstOccurrence, 1348 ResolveError: resolveError, 1349 Context: spanContext, 1350 }) 1351 } 1352 1353 return buildInputs, err 1354 } 1355 1356 func (j *job) isPipelineOrJobPaused(tx Tx) (bool, error) { 1357 if j.paused { 1358 return true, nil 1359 } 1360 1361 var paused bool 1362 err := psql.Select("paused"). 1363 From("pipelines"). 1364 Where(sq.Eq{"id": j.pipelineID}). 1365 RunWith(tx). 1366 QueryRow(). 1367 Scan(&paused) 1368 if err != nil { 1369 return false, err 1370 } 1371 1372 return paused, nil 1373 } 1374 1375 func scanJob(j *job, row scannable) error { 1376 var ( 1377 config sql.NullString 1378 nonce sql.NullString 1379 pipelineInstanceVars sql.NullString 1380 ) 1381 1382 err := row.Scan(&j.id, &j.name, &config, &j.paused, &j.public, &j.firstLoggedBuildID, &j.pipelineID, &j.pipelineName, &pipelineInstanceVars, &j.teamID, &j.teamName, &nonce, pq.Array(&j.tags), &j.hasNewInputs, &j.scheduleRequestedTime, &j.maxInFlight, &j.disableManualTrigger) 1383 if err != nil { 1384 return err 1385 } 1386 1387 if nonce.Valid { 1388 j.nonce = &nonce.String 1389 } 1390 1391 if config.Valid { 1392 j.rawConfig = &config.String 1393 } 1394 1395 if pipelineInstanceVars.Valid { 1396 err = json.Unmarshal([]byte(pipelineInstanceVars.String), &j.pipelineInstanceVars) 1397 if err != nil { 1398 return err 1399 } 1400 } 1401 1402 return nil 1403 } 1404 1405 func scanJobs(conn Conn, lockFactory lock.LockFactory, rows *sql.Rows) (Jobs, error) { 1406 defer Close(rows) 1407 1408 jobs := Jobs{} 1409 1410 for rows.Next() { 1411 job := newEmptyJob(conn, lockFactory) 1412 err := scanJob(job, rows) 1413 if err != nil { 1414 return nil, err 1415 } 1416 1417 jobs = append(jobs, job) 1418 } 1419 1420 return jobs, nil 1421 } 1422 1423 func requestSchedule(tx Tx, jobID int) error { 1424 result, err := psql.Update("jobs"). 1425 Set("schedule_requested", sq.Expr("now()")). 1426 Where(sq.Eq{ 1427 "id": jobID, 1428 }). 1429 RunWith(tx). 1430 Exec() 1431 if err != nil { 1432 return err 1433 } 1434 1435 rowsAffected, err := result.RowsAffected() 1436 if err != nil { 1437 return err 1438 } 1439 1440 if rowsAffected != 1 { 1441 return NonOneRowAffectedError{rowsAffected} 1442 } 1443 1444 return nil 1445 } 1446 1447 // The SELECT query orders the jobs for updating to prevent deadlocking. 1448 // Updating multiple rows using a SELECT subquery does not preserve the same 1449 // order for the updates, which can lead to deadlocking. 1450 func requestScheduleOnDownstreamJobs(tx Tx, jobID int) error { 1451 rows, err := psql.Select("DISTINCT job_id"). 1452 From("job_inputs"). 1453 Where(sq.Eq{ 1454 "passed_job_id": jobID, 1455 }). 1456 OrderBy("job_id DESC"). 1457 RunWith(tx). 1458 Query() 1459 if err != nil { 1460 return err 1461 } 1462 1463 var jobIDs []int 1464 for rows.Next() { 1465 var id int 1466 err = rows.Scan(&id) 1467 if err != nil { 1468 return err 1469 } 1470 1471 jobIDs = append(jobIDs, id) 1472 } 1473 1474 for _, jID := range jobIDs { 1475 _, err := psql.Update("jobs"). 1476 Set("schedule_requested", sq.Expr("now()")). 1477 Where(sq.Eq{ 1478 "id": jID, 1479 }). 1480 RunWith(tx). 1481 Exec() 1482 if err != nil { 1483 return err 1484 } 1485 } 1486 1487 return nil 1488 }