github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/gc/build_log_collector.go (about) 1 package gc 2 3 import ( 4 "context" 5 6 "code.cloudfoundry.org/lager" 7 "code.cloudfoundry.org/lager/lagerctx" 8 9 "time" 10 11 "github.com/pf-qiu/concourse/v6/atc/db" 12 ) 13 14 type buildLogCollector struct { 15 pipelineFactory db.PipelineFactory 16 pipelineLifecycle db.PipelineLifecycle 17 batchSize int 18 drainerConfigured bool 19 buildLogRetentionCalculator BuildLogRetentionCalculator 20 } 21 22 func NewBuildLogCollector( 23 pipelineFactory db.PipelineFactory, 24 pipelineLifecycle db.PipelineLifecycle, 25 batchSize int, 26 buildLogRetentionCalculator BuildLogRetentionCalculator, 27 drainerConfigured bool, 28 ) *buildLogCollector { 29 return &buildLogCollector{ 30 pipelineFactory: pipelineFactory, 31 pipelineLifecycle: pipelineLifecycle, 32 batchSize: batchSize, 33 drainerConfigured: drainerConfigured, 34 buildLogRetentionCalculator: buildLogRetentionCalculator, 35 } 36 } 37 38 func (br *buildLogCollector) Run(ctx context.Context) error { 39 logger := lagerctx.FromContext(ctx).Session("build-reaper") 40 41 logger.Debug("start") 42 defer logger.Debug("done") 43 44 err := br.pipelineLifecycle.RemoveBuildEventsForDeletedPipelines() 45 if err != nil { 46 logger.Error("failed-to-remove-build-events-for-deleted-pipelines", err) 47 return err 48 } 49 50 pipelines, err := br.pipelineFactory.AllPipelines() 51 if err != nil { 52 logger.Error("failed-to-get-pipelines", err) 53 return err 54 } 55 56 for _, pipeline := range pipelines { 57 if pipeline.Paused() { 58 continue 59 } 60 61 jobs, err := pipeline.Jobs() 62 if err != nil { 63 logger.Error("failed-to-get-dashboard", err) 64 return err 65 } 66 67 for _, job := range jobs { 68 err = br.reapLogsOfJob(pipeline, job, logger) 69 if err != nil { 70 return err 71 } 72 } 73 } 74 75 return nil 76 } 77 78 func (br *buildLogCollector) reapLogsOfJob(pipeline db.Pipeline, 79 job db.Job, 80 logger lager.Logger) error { 81 82 jobConfig, err := job.Config() 83 if err != nil { 84 logger.Error("failed-to-get-job-config", err) 85 return err 86 } 87 88 logRetention := br.buildLogRetentionCalculator.BuildLogsToRetain(jobConfig) 89 if logRetention.Builds == 0 && logRetention.Days == 0 { 90 return nil 91 } 92 93 buildsToConsiderDeleting := []db.Build{} 94 95 from := job.FirstLoggedBuildID() 96 limit := br.batchSize 97 page := &db.Page{From: &from, Limit: limit} 98 for page != nil { 99 builds, pagination, err := job.Builds(*page) 100 if err != nil { 101 logger.Error("failed-to-get-job-builds-to-delete", err) 102 return err 103 } 104 105 buildsOfBatch := []db.Build{} 106 for _, build := range builds { 107 // Ignore reaped builds 108 if !build.ReapTime().IsZero() { 109 continue 110 } 111 112 buildsOfBatch = append(buildsOfBatch, build) 113 } 114 buildsToConsiderDeleting = append(buildsOfBatch, buildsToConsiderDeleting...) 115 116 page = pagination.Newer 117 } 118 119 logger.Debug("after-first-round-filter", lager.Data{ 120 "builds_to_consider_deleting": len(buildsToConsiderDeleting), 121 }) 122 123 if len(buildsToConsiderDeleting) == 0 { 124 return nil 125 } 126 127 buildIDsToDelete := []int{} 128 toRetainNonSucceededBuildIDs := []int{} 129 retainedBuilds := 0 130 retainedSucceededBuilds := 0 131 firstLoggedBuildID := 0 132 for _, build := range buildsToConsiderDeleting { 133 // Running build should not be reaped. 134 if build.IsRunning() { 135 firstLoggedBuildID = build.ID() 136 continue 137 } 138 139 if logRetention.Days > 0 { 140 if !build.EndTime().IsZero() && build.EndTime().AddDate(0, 0, logRetention.Days).Before(time.Now()) { 141 logger.Debug("should-reap-due-to-days", build.LagerData()) 142 buildIDsToDelete = append(buildIDsToDelete, build.ID()) 143 continue 144 } 145 } 146 147 // Before a build is drained, it should not be reaped. 148 if br.drainerConfigured { 149 if !build.IsDrained() { 150 firstLoggedBuildID = build.ID() 151 continue 152 } 153 } 154 155 // If Builds is 0, then all builds are retained, so we don't need to 156 // check MinSuccessBuilds at all. 157 if logRetention.Builds > 0 { 158 if logRetention.MinimumSucceededBuilds > 0 && build.Status() == db.BuildStatusSucceeded { 159 if retainedSucceededBuilds < logRetention.MinimumSucceededBuilds { 160 retainedBuilds++ 161 retainedSucceededBuilds++ 162 firstLoggedBuildID = build.ID() 163 continue 164 } 165 } 166 167 if retainedBuilds < logRetention.Builds { 168 retainedBuilds++ 169 toRetainNonSucceededBuildIDs = append(toRetainNonSucceededBuildIDs, build.ID()) 170 firstLoggedBuildID = build.ID() 171 continue 172 } 173 174 buildIDsToDelete = append(buildIDsToDelete, build.ID()) 175 } 176 } 177 178 logger.Debug("after-second-round-filter", lager.Data{ 179 "retained_builds": retainedBuilds, 180 "retained_succeeded_builds": retainedSucceededBuilds, 181 }) 182 183 if len(buildIDsToDelete) == 0 { 184 logger.Debug("no-builds-to-reap") 185 return nil 186 } 187 188 // If this happens, firstLoggedBuildID must points to a success build, thus 189 // no need to update firstLoggedBuildID. 190 if retainedBuilds > logRetention.Builds { 191 logger.Debug("more-builds-to-retain", lager.Data{ 192 "retained_builds": retainedBuilds, 193 }) 194 delta := retainedBuilds - logRetention.Builds 195 n := len(toRetainNonSucceededBuildIDs) 196 for i := 1; i <= delta; i++ { 197 buildIDsToDelete = append(buildIDsToDelete, toRetainNonSucceededBuildIDs[n-i]) 198 } 199 } 200 201 logger.Debug("reaping-builds", lager.Data{ 202 "build_ids": buildIDsToDelete, 203 }) 204 205 err = pipeline.DeleteBuildEventsByBuildIDs(buildIDsToDelete) 206 if err != nil { 207 logger.Error("failed-to-delete-build-events", err) 208 return err 209 } 210 211 if firstLoggedBuildID > job.FirstLoggedBuildID() { 212 err = job.UpdateFirstLoggedBuildID(firstLoggedBuildID) 213 if err != nil { 214 logger.Error("failed-to-update-first-logged-build-id", err) 215 return err 216 } 217 } 218 219 return nil 220 }