github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/scheduler/algorithm/compute.go (about) 1 package algorithm 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/pf-qiu/concourse/v6/atc/db" 8 "github.com/pf-qiu/concourse/v6/tracing" 9 ) 10 11 type Resolver interface { 12 Resolve(context.Context) (map[string]*versionCandidate, db.ResolutionFailure, error) 13 InputConfigs() db.InputConfigs 14 } 15 16 func New(versionsDB db.VersionsDB) *Algorithm { 17 return &Algorithm{ 18 versionsDB: versionsDB, 19 } 20 } 21 22 type Algorithm struct { 23 versionsDB db.VersionsDB 24 } 25 26 func (a *Algorithm) Compute( 27 ctx context.Context, 28 job db.Job, 29 inputs db.InputConfigs, 30 ) (db.InputMapping, bool, bool, error) { 31 ctx, span := tracing.StartSpan(ctx, "Algorithm.Compute", tracing.Attrs{ 32 "pipeline": job.PipelineName(), 33 "job": job.Name(), 34 }) 35 defer span.End() 36 37 resolvers, err := constructResolvers(a.versionsDB, inputs) 38 if err != nil { 39 return nil, false, false, fmt.Errorf("construct resolvers: %w", err) 40 } 41 42 return a.computeResolvers(ctx, resolvers) 43 } 44 45 func (a *Algorithm) computeResolvers( 46 ctx context.Context, 47 resolvers []Resolver, 48 ) (db.InputMapping, bool, bool, error) { 49 finalHasNext := false 50 finalResolved := true 51 finalMapping := db.InputMapping{} 52 53 for _, resolver := range resolvers { 54 versionCandidates, resolveErr, err := resolver.Resolve(ctx) 55 if err != nil { 56 return nil, false, false, fmt.Errorf("resolve: %w", err) 57 } 58 59 // determines if the algorithm successfully resolved all inputs depending 60 // on if all resolvers did not return a resolve error 61 finalResolved = finalResolved && (resolveErr == "") 62 63 // converts the version candidates into an object that is recognizable by 64 // other components. also computes the first occurrence for all satisfiable 65 // inputs 66 finalMapping, err = a.candidatesToInputMapping(ctx, finalMapping, resolver.InputConfigs(), versionCandidates, resolveErr) 67 if err != nil { 68 return nil, false, false, fmt.Errorf("candidates to input mapping: %w", err) 69 } 70 71 // if any one of the resolvers has a version candidate that has an unused 72 // next every version, the algorithm should return true for being able to 73 // be run again 74 finalHasNext = finalHasNext || a.finalizeHasNext(versionCandidates) 75 } 76 77 return finalMapping, finalResolved, finalHasNext, nil 78 } 79 80 func (a *Algorithm) finalizeHasNext(versionCandidates map[string]*versionCandidate) bool { 81 hasNextCombined := false 82 for _, candidate := range versionCandidates { 83 hasNextCombined = hasNextCombined || candidate.HasNextEveryVersion 84 } 85 86 return hasNextCombined 87 } 88 89 func (a *Algorithm) candidatesToInputMapping(ctx context.Context, mapping db.InputMapping, inputConfigs db.InputConfigs, candidates map[string]*versionCandidate, resolveErr db.ResolutionFailure) (db.InputMapping, error) { 90 for _, input := range inputConfigs { 91 if resolveErr != "" { 92 mapping[input.Name] = db.InputResult{ 93 ResolveError: resolveErr, 94 } 95 } else { 96 firstOcc, err := a.versionsDB.IsFirstOccurrence(ctx, input.JobID, input.Name, candidates[input.Name].Version, input.ResourceID) 97 if err != nil { 98 return nil, err 99 } 100 101 mapping[input.Name] = db.InputResult{ 102 Input: &db.AlgorithmInput{ 103 AlgorithmVersion: db.AlgorithmVersion{ 104 ResourceID: input.ResourceID, 105 Version: candidates[input.Name].Version, 106 }, 107 FirstOccurrence: firstOcc, 108 }, 109 PassedBuildIDs: candidates[input.Name].SourceBuildIds, 110 } 111 } 112 } 113 114 return mapping, nil 115 }