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  }