github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/gc/build_log_collector_test.go (about) 1 package gc_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/pf-qiu/concourse/v6/atc" 10 "github.com/pf-qiu/concourse/v6/atc/db" 11 "github.com/pf-qiu/concourse/v6/atc/db/dbfakes" 12 . "github.com/pf-qiu/concourse/v6/atc/gc" 13 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 ) 17 18 var _ = Describe("BuildLogCollector", func() { 19 var ( 20 buildLogCollector GcCollector 21 fakePipelineFactory *dbfakes.FakePipelineFactory 22 fakePipelineLifecycle *dbfakes.FakePipelineLifecycle 23 batchSize int 24 buildLogRetainCalc BuildLogRetentionCalculator 25 ) 26 27 BeforeEach(func() { 28 fakePipelineFactory = new(dbfakes.FakePipelineFactory) 29 fakePipelineLifecycle = new(dbfakes.FakePipelineLifecycle) 30 batchSize = 5 31 buildLogRetainCalc = NewBuildLogRetentionCalculator(0, 0, 0, 0) 32 }) 33 34 JustBeforeEach(func() { 35 buildLogCollector = NewBuildLogCollector( 36 fakePipelineFactory, 37 fakePipelineLifecycle, 38 batchSize, 39 buildLogRetainCalc, 40 false, 41 ) 42 }) 43 44 It("removes build events from deleted pipelines", func() { 45 err := buildLogCollector.Run(context.TODO()) 46 Expect(err).ToNot(HaveOccurred()) 47 Expect(fakePipelineLifecycle.RemoveBuildEventsForDeletedPipelinesCallCount()).To(Equal(1)) 48 }) 49 50 Context("when removing build events from deleted pipelines fails", func() { 51 BeforeEach(func() { 52 fakePipelineLifecycle.RemoveBuildEventsForDeletedPipelinesReturns(errors.New("error")) 53 }) 54 55 It("errors", func() { 56 err := buildLogCollector.Run(context.TODO()) 57 Expect(err).To(HaveOccurred()) 58 }) 59 }) 60 61 Context("when there is a pipeline", func() { 62 var fakePipeline *dbfakes.FakePipeline 63 64 BeforeEach(func() { 65 fakePipeline = new(dbfakes.FakePipeline) 66 fakePipeline.IDReturns(42) 67 68 fakePipelineFactory.AllPipelinesReturns([]db.Pipeline{fakePipeline}, nil) 69 }) 70 71 Context("when getting the dashboard fails", func() { 72 var disaster error 73 74 BeforeEach(func() { 75 disaster = errors.New("sorry pal") 76 fakePipeline.JobsReturns(nil, disaster) 77 }) 78 79 It("returns the error", func() { 80 err := buildLogCollector.Run(context.TODO()) 81 Expect(err).To(Equal(disaster)) 82 }) 83 }) 84 85 Context("when the dashboard has a job", func() { 86 var fakeJob *dbfakes.FakeJob 87 88 BeforeEach(func() { 89 fakeJob = new(dbfakes.FakeJob) 90 fakeJob.NameReturns("job-1") 91 fakeJob.FirstLoggedBuildIDReturns(5) 92 fakeJob.ConfigReturns(atc.JobConfig{ 93 BuildLogsToRetain: 2, 94 }, nil) 95 96 fakePipeline.JobsReturns([]db.Job{fakeJob}, nil) 97 }) 98 99 Context("drain handling", func() { 100 JustBeforeEach(func() { 101 buildLogCollector = NewBuildLogCollector( 102 fakePipelineFactory, 103 fakePipelineLifecycle, 104 batchSize, 105 buildLogRetainCalc, 106 true, 107 ) 108 }) 109 BeforeEach(func() { 110 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 111 if *page.From == 5 { 112 return []db.Build{sbDrained(9, false), sbDrained(8, false), sbDrained(7, true), sbDrained(6, false), sbDrained(5, true)}, db.Pagination{Newer: &db.Page{From: db.NewIntPtr(10), Limit: 5}}, nil 113 } else if *page.From == 10 { 114 return []db.Build{sbDrained(11, true), sbDrained(10, true)}, db.Pagination{}, nil 115 } 116 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 117 return []db.Build{}, db.Pagination{}, nil 118 } 119 120 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 121 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 122 }) 123 124 JustBeforeEach(func() { 125 err := buildLogCollector.Run(context.TODO()) 126 Expect(err).NotTo(HaveOccurred()) 127 }) 128 129 It("should not reap builds which have not been drained", func() { 130 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 131 132 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(6))) 133 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(8))) 134 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(9))) 135 }) 136 137 It("should reap builds which have been drained", func() { 138 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 139 140 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).To(ConsistOf(7, 5)) 141 }) 142 143 It("should update first logged build id to the earliest non-drained build", func() { 144 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 145 146 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(1)) 147 actualNewFirstLoggedBuildID := fakeJob.UpdateFirstLoggedBuildIDArgsForCall(0) 148 Expect(actualNewFirstLoggedBuildID).To(Equal(6)) 149 }) 150 }) 151 152 Context("when drain has not been configured", func() { 153 BeforeEach(func() { 154 buildLogCollector = NewBuildLogCollector( 155 fakePipelineFactory, 156 fakePipelineLifecycle, 157 batchSize, 158 buildLogRetainCalc, 159 false, 160 ) 161 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 162 if *page.From == 5 { 163 return []db.Build{sbDrained(9, true), sbDrained(8, false), sbDrained(7, false), sbDrained(6, true), sbDrained(5, false)}, db.Pagination{Newer: &db.Page{From: db.NewIntPtr(10), Limit: 5}}, nil 164 } else if *page.From == 10 { 165 return []db.Build{sbDrained(10, true)}, db.Pagination{}, nil 166 } 167 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 168 return []db.Build{}, db.Pagination{}, nil 169 } 170 171 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 172 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 173 }) 174 It("should reap builds if draining is not configured", func() { 175 err := buildLogCollector.Run(context.TODO()) 176 Expect(err).NotTo(HaveOccurred()) 177 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 178 179 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).To(ConsistOf(5, 6, 7, 8)) 180 181 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(1)) 182 actualNewFirstLoggedBuildID := fakeJob.UpdateFirstLoggedBuildIDArgsForCall(0) 183 Expect(actualNewFirstLoggedBuildID).To(Equal(9)) 184 }) 185 }) 186 187 Context("when deleting build events fails", func() { 188 var disaster error 189 190 BeforeEach(func() { 191 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 192 if *page.From == 5 { 193 return []db.Build{sbDrained(8, false), sbDrained(7, true), sbDrained(6, false), sbDrained(5, false)}, db.Pagination{}, nil 194 } 195 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 196 return []db.Build{}, db.Pagination{}, nil 197 } 198 199 disaster = errors.New("major malfunction") 200 201 fakePipeline.DeleteBuildEventsByBuildIDsReturns(disaster) 202 }) 203 204 It("returns the error", func() { 205 err := buildLogCollector.Run(context.TODO()) 206 Expect(err).To(Equal(disaster)) 207 }) 208 209 It("does not update first logged build id", func() { 210 buildLogCollector.Run(context.TODO()) 211 212 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(BeZero()) 213 }) 214 }) 215 216 Context("when updating first logged build id fails", func() { 217 var disaster error 218 219 BeforeEach(func() { 220 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 221 if *page.From == 5 { 222 return []db.Build{sbDrained(8, false), sbDrained(7, true), sbDrained(6, false), sbDrained(5, false)}, db.Pagination{}, nil 223 } 224 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 225 return []db.Build{}, db.Pagination{}, nil 226 } 227 228 disaster = errors.New("major malfunction") 229 230 fakeJob.UpdateFirstLoggedBuildIDReturns(disaster) 231 }) 232 233 It("returns the error", func() { 234 err := buildLogCollector.Run(context.TODO()) 235 Expect(err).To(Equal(disaster)) 236 }) 237 }) 238 239 Context("when the builds we want to reap are still running", func() { 240 BeforeEach(func() { 241 fakeJob.ConfigReturns(atc.JobConfig{ 242 BuildLogsToRetain: 3, 243 }, nil) 244 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 245 if *page.From == 5 { 246 return []db.Build{ 247 runningBuild(9), 248 runningBuild(8), 249 sb(7), 250 sb(6), 251 sb(5), 252 }, db.Pagination{Newer: &db.Page{From: db.NewIntPtr(10), Limit: 5}}, nil 253 } else if *page.From == 10 { 254 return []db.Build{sb(10)}, db.Pagination{}, nil 255 } else { 256 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 257 } 258 return nil, db.Pagination{}, nil 259 } 260 261 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 262 263 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 264 }) 265 266 JustBeforeEach(func() { 267 err := buildLogCollector.Run(context.TODO()) 268 Expect(err).NotTo(HaveOccurred()) 269 }) 270 271 It("reaps only not-running builds", func() { 272 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 273 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 274 Expect(actualBuildIDs).To(ConsistOf(5)) 275 }) 276 277 It("updates FirstLoggedBuildID to earliest non-reaped build", func() { 278 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(1)) 279 actualNewFirstLoggedBuildID := fakeJob.UpdateFirstLoggedBuildIDArgsForCall(0) 280 Expect(actualNewFirstLoggedBuildID).To(Equal(6)) 281 }) 282 }) 283 284 Context("when no builds need to be reaped", func() { 285 BeforeEach(func() { 286 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 287 if *page.From == 5 { 288 return []db.Build{runningBuild(5)}, db.Pagination{}, nil 289 } else { 290 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 291 } 292 return nil, db.Pagination{}, nil 293 } 294 295 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 296 297 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 298 }) 299 300 JustBeforeEach(func() { 301 err := buildLogCollector.Run(context.TODO()) 302 Expect(err).NotTo(HaveOccurred()) 303 }) 304 305 It("doesn't reap any builds", func() { 306 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(BeZero()) 307 }) 308 309 It("doesn't update FirstLoggedBuildID", func() { 310 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(BeZero()) 311 }) 312 }) 313 314 Context("when no builds exist", func() { 315 BeforeEach(func() { 316 fakeJob.BuildsReturns(nil, db.Pagination{}, nil) 317 318 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 319 320 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 321 }) 322 323 It("doesn't reap any builds", func() { 324 err := buildLogCollector.Run(context.TODO()) 325 Expect(err).NotTo(HaveOccurred()) 326 327 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(BeZero()) 328 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(BeZero()) 329 }) 330 }) 331 332 Context("when getting the job builds fails", func() { 333 var disaster error 334 335 BeforeEach(func() { 336 disaster = errors.New("major malfunction") 337 338 fakeJob.BuildsReturns(nil, db.Pagination{}, disaster) 339 }) 340 341 It("returns the error", func() { 342 err := buildLogCollector.Run(context.TODO()) 343 Expect(err).To(Equal(disaster)) 344 }) 345 }) 346 347 Context("when only count is set", func() { 348 BeforeEach(func() { 349 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 350 if *page.From == 5 { 351 return []db.Build{sbTime(6, time.Now().Add(-23*time.Hour)), sbTime(5, time.Now().Add(-49*time.Hour))}, db.Pagination{}, nil 352 } 353 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 354 return nil, db.Pagination{}, nil 355 } 356 357 fakeJob.ConfigReturns(atc.JobConfig{ 358 BuildLogRetention: &atc.BuildLogRetention{ 359 Builds: 1, 360 Days: 0, 361 }, 362 }, nil) 363 364 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 365 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 366 }) 367 368 It("should delete 1 build event", func() { 369 err := buildLogCollector.Run(context.TODO()) 370 Expect(err).NotTo(HaveOccurred()) 371 372 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 373 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 374 Expect(actualBuildIDs).To(ConsistOf(5)) 375 }) 376 }) 377 378 Context("when only date is set", func() { 379 BeforeEach(func() { 380 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 381 if *page.From == 5 { 382 return []db.Build{sbTime(6, time.Now().Add(-23*time.Hour)), sbTime(5, time.Now().Add(-49*time.Hour))}, db.Pagination{}, nil 383 } 384 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 385 return nil, db.Pagination{}, nil 386 } 387 388 fakeJob.ConfigReturns(atc.JobConfig{ 389 BuildLogRetention: &atc.BuildLogRetention{ 390 Builds: 0, 391 Days: 3, 392 }, 393 }, nil) 394 395 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 396 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 397 }) 398 399 It("should delete nothing, because of the date retention", func() { 400 err := buildLogCollector.Run(context.TODO()) 401 Expect(err).NotTo(HaveOccurred()) 402 403 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(0)) 404 }) 405 }) 406 407 Context("when count and date are set > 0", func() { 408 BeforeEach(func() { 409 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 410 if *page.From == 5 { 411 return []db.Build{sbTime(6, time.Now().Add(-23*time.Hour)), sbTime(5, time.Now().Add(-49*time.Hour))}, db.Pagination{}, nil 412 } 413 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 414 return nil, db.Pagination{}, nil 415 } 416 417 fakeJob.ConfigReturns(atc.JobConfig{ 418 BuildLogRetention: &atc.BuildLogRetention{ 419 Builds: 1, 420 Days: 3, 421 }, 422 }, nil) 423 424 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 425 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 426 }) 427 428 It("should delete 1 build, because of the builds retention", func() { 429 err := buildLogCollector.Run(context.TODO()) 430 Expect(err).NotTo(HaveOccurred()) 431 432 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 433 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 434 Expect(actualBuildIDs).To(ConsistOf(5)) 435 }) 436 }) 437 438 Context("when only date is set", func() { 439 BeforeEach(func() { 440 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 441 if *page.From == 5 { 442 return []db.Build{sbTime(6, time.Now().Add(-23*time.Hour)), sbTime(5, time.Now().Add(-49*time.Hour))}, db.Pagination{}, nil 443 } 444 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 445 return nil, db.Pagination{}, nil 446 } 447 448 fakeJob.ConfigReturns(atc.JobConfig{ 449 BuildLogRetention: &atc.BuildLogRetention{ 450 Builds: 0, 451 Days: 1, 452 }, 453 }, nil) 454 455 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 456 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 457 }) 458 459 It("should delete before that", func() { 460 err := buildLogCollector.Run(context.TODO()) 461 Expect(err).NotTo(HaveOccurred()) 462 463 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 464 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 465 Expect(actualBuildIDs).To(ConsistOf(5)) 466 }) 467 }) 468 469 Context("when min_success_build is set", func() { 470 BeforeEach(func() { 471 fakeJob.ConfigReturns(atc.JobConfig{ 472 BuildLogRetention: &atc.BuildLogRetention{ 473 Builds: 5, 474 Days: 0, 475 MinimumSucceededBuilds: 2, 476 }, 477 }, nil) 478 479 page1 := db.Page{From: db.NewIntPtr(5), Limit: 5} 480 page2 := db.Page{From: db.NewIntPtr(10), Limit: 5} 481 page3 := db.Page{From: db.NewIntPtr(15), Limit: 5} 482 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 483 if *page.From == *page1.From { 484 return []db.Build{sb(9), successBuild(8), sb(7), reapedBuild(6), reapedBuild(5)}, db.Pagination{Newer: &page2}, nil 485 } else if *page.From == *page2.From { 486 return []db.Build{sb(14), successBuild(13), sb(12), sb(11), sb(10)}, db.Pagination{Newer: &page3}, nil 487 } else if *page.From == *page3.From { 488 return []db.Build{sb(18), sb(17), sb(16), sb(15)}, db.Pagination{}, nil 489 } 490 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 491 return nil, db.Pagination{}, nil 492 } 493 }) 494 495 JustBeforeEach(func() { 496 err := buildLogCollector.Run(context.TODO()) 497 Expect(err).NotTo(HaveOccurred()) 498 }) 499 500 It("should reap non success builds", func() { 501 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 502 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 503 Expect(actualBuildIDs).To(ConsistOf(7, 9, 10, 11, 12, 14, 15)) 504 505 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(5))) 506 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(6))) 507 }) 508 509 It("should keep at least n success builds, n=MinSuccessBuilds, n=2 ", func() { 510 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(8))) 511 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(13))) 512 }) 513 514 It("should update first logged build id to the earliest success build", func() { 515 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(1)) 516 actualNewFirstLoggedBuildID := fakeJob.UpdateFirstLoggedBuildIDArgsForCall(0) 517 Expect(actualNewFirstLoggedBuildID).To(Equal(8)) 518 }) 519 }) 520 521 Context("when min_success_build equals builds", func() { 522 BeforeEach(func() { 523 fakeJob.ConfigReturns(atc.JobConfig{ 524 BuildLogRetention: &atc.BuildLogRetention{ 525 Builds: 5, 526 Days: 0, 527 MinimumSucceededBuilds: 5, 528 }, 529 }, nil) 530 531 page1 := db.Page{From: db.NewIntPtr(5), Limit: 5} 532 page2 := db.Page{From: db.NewIntPtr(10), Limit: 5} 533 page3 := db.Page{From: db.NewIntPtr(15), Limit: 5} 534 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 535 if *page.From == *page1.From { 536 return []db.Build{sb(9), successBuild(8), sb(7), reapedBuild(6), reapedBuild(5)}, db.Pagination{Newer: &page2}, nil 537 } else if *page.From == *page2.From { 538 return []db.Build{sb(14), successBuild(13), successBuild(12), sb(11), successBuild(10)}, db.Pagination{Newer: &page3}, nil 539 } else if *page.From == *page3.From { 540 return []db.Build{successBuild(18), sb(17), sb(16), successBuild(15)}, db.Pagination{}, nil 541 } 542 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 543 return nil, db.Pagination{}, nil 544 } 545 }) 546 547 JustBeforeEach(func() { 548 err := buildLogCollector.Run(context.TODO()) 549 Expect(err).NotTo(HaveOccurred()) 550 }) 551 552 It("should reap non success builds and success builds that exceeds min success build retained number", func() { 553 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 554 actualBuildIDs := fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0) 555 Expect(actualBuildIDs).To(ConsistOf(7, 8, 9, 11, 14, 16, 17)) 556 557 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(5))) 558 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(6))) 559 }) 560 561 It("should keep at least n success builds, n=MinSuccessBuilds, n=5", func() { 562 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(10))) 563 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(12))) 564 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(13))) 565 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(15))) 566 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).Should(Not(ContainElement(18))) 567 }) 568 569 It("should update first logged build id to the earliest success build", func() { 570 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(1)) 571 actualNewFirstLoggedBuildID := fakeJob.UpdateFirstLoggedBuildIDArgsForCall(0) 572 Expect(actualNewFirstLoggedBuildID).To(Equal(10)) 573 }) 574 }) 575 }) 576 577 Context("when the FirstLoggedBuildID has an value", func() { 578 Context("when all the logs get reaped", func() { 579 var fakeJob *dbfakes.FakeJob 580 581 BeforeEach(func() { 582 fakeJob = new(dbfakes.FakeJob) 583 fakeJob.NameReturns("job-1") 584 fakeJob.FirstLoggedBuildIDReturns(5) 585 fakeJob.ConfigReturns(atc.JobConfig{ 586 BuildLogRetention: &atc.BuildLogRetention{ 587 Days: 1, 588 }, 589 }, nil) 590 591 fakePipeline.JobsReturns([]db.Job{fakeJob}, nil) 592 593 yesterday := time.Now().Add(-30 * time.Hour) 594 595 fakeJob.BuildsReturns([]db.Build{sbTime(9, yesterday), sbTime(8, yesterday), sbTime(7, yesterday), sbTime(6, yesterday), sbTime(5, yesterday)}, db.Pagination{}, nil) 596 }) 597 598 It("FirstLoggedBuildID doesn't get reset to 0", func() { 599 Expect(buildLogCollector.Run(context.TODO())).NotTo(HaveOccurred()) 600 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 601 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).To(ConsistOf(9, 8, 7, 6, 5)) 602 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(Equal(0)) 603 }) 604 }) 605 606 Context("when FirstLoggedBuildID == 1", func() { 607 var fakeJob *dbfakes.FakeJob 608 609 BeforeEach(func() { 610 fakeJob = new(dbfakes.FakeJob) 611 fakeJob.NameReturns("job-1") 612 fakeJob.FirstLoggedBuildIDReturns(1) 613 fakeJob.ConfigReturns(atc.JobConfig{ 614 BuildLogsToRetain: 10, 615 }, nil) 616 617 fakePipeline.JobsReturns([]db.Job{fakeJob}, nil) 618 }) 619 620 Context("when we install a custom build log retention calculator", func() { 621 BeforeEach(func() { 622 buildLogRetainCalc = NewBuildLogRetentionCalculator(3, 3, 0, 0) 623 624 fakeJob.BuildsStub = func(page db.Page) ([]db.Build, db.Pagination, error) { 625 if *page.From == 1 { 626 return []db.Build{sb(4), sb(3), sb(2), sb(1)}, db.Pagination{}, nil 627 } 628 629 Fail(fmt.Sprintf("Builds called with unexpected argument: page=%#v", page)) 630 return nil, db.Pagination{}, nil 631 } 632 633 fakePipeline.DeleteBuildEventsByBuildIDsReturns(nil) 634 fakeJob.UpdateFirstLoggedBuildIDReturns(nil) 635 }) 636 637 It("uses build log calculator", func() { 638 Expect(buildLogCollector.Run(context.TODO())).NotTo(HaveOccurred()) 639 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(Equal(1)) 640 Expect(fakePipeline.DeleteBuildEventsByBuildIDsArgsForCall(0)).To(ConsistOf(1)) 641 }) 642 }) 643 644 Context("when getting the job builds fails", func() { 645 var disaster error 646 647 BeforeEach(func() { 648 disaster = errors.New("major malfunction") 649 650 fakeJob.BuildsReturns(nil, db.Pagination{}, disaster) 651 }) 652 653 It("returns the error", func() { 654 err := buildLogCollector.Run(context.TODO()) 655 Expect(err).To(Equal(disaster)) 656 }) 657 }) 658 }) 659 660 }) 661 662 Context("when the job says retain 0 builds", func() { 663 var fakeJob *dbfakes.FakeJob 664 665 BeforeEach(func() { 666 fakeJob = new(dbfakes.FakeJob) 667 fakeJob.NameReturns("job-1") 668 fakeJob.FirstLoggedBuildIDReturns(6) 669 fakeJob.ConfigReturns(atc.JobConfig{ 670 BuildLogsToRetain: 0, 671 }, nil) 672 fakeJob.TagsReturns([]string{}) 673 674 fakePipeline.JobsReturns([]db.Job{fakeJob}, nil) 675 }) 676 677 It("skips the reaping step for that job", func() { 678 err := buildLogCollector.Run(context.TODO()) 679 Expect(err).NotTo(HaveOccurred()) 680 681 Expect(fakeJob.BuildsCallCount()).To(BeZero()) 682 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(BeZero()) 683 Expect(fakeJob.UpdateFirstLoggedBuildIDCallCount()).To(BeZero()) 684 }) 685 }) 686 }) 687 688 Context("when there is a paused pipeline", func() { 689 var fakePipeline *dbfakes.FakePipeline 690 691 BeforeEach(func() { 692 fakePipeline = new(dbfakes.FakePipeline) 693 fakePipeline.IDReturns(42) 694 fakePipeline.PausedReturns(true) 695 696 fakePipelineFactory.AllPipelinesReturns([]db.Pipeline{fakePipeline}, nil) 697 }) 698 699 It("skips the reaping step for that pipeline", func() { 700 err := buildLogCollector.Run(context.TODO()) 701 Expect(err).NotTo(HaveOccurred()) 702 703 Expect(fakePipeline.DeleteBuildEventsByBuildIDsCallCount()).To(BeZero()) 704 }) 705 }) 706 707 Context("when getting the pipelines fails", func() { 708 var disaster error 709 710 BeforeEach(func() { 711 disaster = errors.New("major malfunction") 712 713 fakePipelineFactory.AllPipelinesReturns(nil, disaster) 714 }) 715 716 It("returns the error", func() { 717 err := buildLogCollector.Run(context.TODO()) 718 Expect(err).To(Equal(disaster)) 719 }) 720 }) 721 722 }) 723 724 func sb(id int) db.Build { 725 build := new(dbfakes.FakeBuild) 726 build.IDReturns(id) 727 build.IsRunningReturns(false) 728 return build 729 } 730 731 func sbTime(id int, end time.Time) db.Build { 732 build := new(dbfakes.FakeBuild) 733 build.IDReturns(id) 734 build.EndTimeReturns(end) 735 build.IsRunningReturns(false) 736 return build 737 } 738 739 func sbDrained(id int, drained bool) db.Build { 740 build := new(dbfakes.FakeBuild) 741 build.IsDrainedReturns(drained) 742 build.IDReturns(id) 743 build.IsRunningReturns(false) 744 return build 745 } 746 747 func runningBuild(id int) db.Build { 748 build := new(dbfakes.FakeBuild) 749 build.IDReturns(id) 750 build.IsRunningReturns(true) 751 return build 752 } 753 754 func reapedBuild(id int) db.Build { 755 build := new(dbfakes.FakeBuild) 756 build.IDReturns(id) 757 build.ReapTimeReturns(time.Now()) 758 return build 759 } 760 761 func successBuild(id int) db.Build { 762 build := new(dbfakes.FakeBuild) 763 build.IDReturns(id) 764 build.StatusReturns(db.BuildStatusSucceeded) 765 return build 766 }