(Patch contributed by an anonymous contributor) Change-Id: I29fd7dd008d4e509ea074a38d3948946b26da7ab
		
			
				
	
	
		
			220 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package submitqueue
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/apex/log"
 | 
						|
 | 
						|
	"github.com/tweag/gerrit-queue/gerrit"
 | 
						|
)
 | 
						|
 | 
						|
// Runner is a struct existing across the lifetime of a single run of the submit queue
 | 
						|
// it contains a mutex to avoid being run multiple times.
 | 
						|
// In fact, it even cancels runs while another one is still in progress.
 | 
						|
// It contains a Gerrit object facilitating access, a log object, the configured submit queue tag
 | 
						|
// and a `wipSerie` (only populated if waiting for a rebase)
 | 
						|
type Runner struct {
 | 
						|
	mut              sync.Mutex
 | 
						|
	currentlyRunning bool
 | 
						|
	wipSerie         *gerrit.Serie
 | 
						|
	logger           *log.Logger
 | 
						|
	gerrit           *gerrit.Client
 | 
						|
}
 | 
						|
 | 
						|
// NewRunner creates a new Runner struct
 | 
						|
func NewRunner(logger *log.Logger, gerrit *gerrit.Client) *Runner {
 | 
						|
	return &Runner{
 | 
						|
		logger: logger,
 | 
						|
		gerrit: gerrit,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// isAutoSubmittable determines if something could be autosubmitted, potentially requiring a rebase
 | 
						|
// for this, it needs to:
 | 
						|
//  * have the "Autosubmit" label set to +1
 | 
						|
//  * have gerrit's 'submittable' field set to true
 | 
						|
// it doesn't check if the series is rebased on HEAD
 | 
						|
func (r *Runner) isAutoSubmittable(s *gerrit.Serie) bool {
 | 
						|
	for _, c := range s.ChangeSets {
 | 
						|
		if c.Submittable != true || !c.IsAutosubmit() {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// IsCurrentlyRunning returns true if the runner is currently running
 | 
						|
func (r *Runner) IsCurrentlyRunning() bool {
 | 
						|
	return r.currentlyRunning
 | 
						|
}
 | 
						|
 | 
						|
// GetWIPSerie returns the current wipSerie, if any, nil otherwiese
 | 
						|
// Acquires a lock, so check with IsCurrentlyRunning first
 | 
						|
func (r *Runner) GetWIPSerie() *gerrit.Serie {
 | 
						|
	r.mut.Lock()
 | 
						|
	defer func() {
 | 
						|
		r.mut.Unlock()
 | 
						|
	}()
 | 
						|
	return r.wipSerie
 | 
						|
}
 | 
						|
 | 
						|
// Trigger gets triggered periodically
 | 
						|
func (r *Runner) Trigger(fetchOnly bool) error {
 | 
						|
	// TODO: If CI fails, remove the auto-submit labels => rules.pl
 | 
						|
	// Only one trigger can run at the same time
 | 
						|
	r.mut.Lock()
 | 
						|
	if r.currentlyRunning {
 | 
						|
		return fmt.Errorf("Already running, skipping")
 | 
						|
	}
 | 
						|
	r.currentlyRunning = true
 | 
						|
	r.mut.Unlock()
 | 
						|
	defer func() {
 | 
						|
		r.mut.Lock()
 | 
						|
		r.currentlyRunning = false
 | 
						|
		r.mut.Unlock()
 | 
						|
	}()
 | 
						|
 | 
						|
	// Prepare the work by creating a local cache of gerrit state
 | 
						|
	err := r.gerrit.Refresh()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// early return if we only want to fetch
 | 
						|
	if fetchOnly {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if r.wipSerie != nil {
 | 
						|
		// refresh wipSerie with how it looks like in gerrit now
 | 
						|
		wipSerie := r.gerrit.FindSerie(func(s *gerrit.Serie) bool {
 | 
						|
			// the new wipSerie needs to have the same number of changesets
 | 
						|
			if len(r.wipSerie.ChangeSets) != len(s.ChangeSets) {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			// … and the same ChangeIDs.
 | 
						|
			for idx, c := range s.ChangeSets {
 | 
						|
				if r.wipSerie.ChangeSets[idx].ChangeID != c.ChangeID {
 | 
						|
					return false
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return true
 | 
						|
		})
 | 
						|
		if wipSerie == nil {
 | 
						|
			r.logger.WithField("wipSerie", r.wipSerie).Warn("wipSerie has disappeared")
 | 
						|
			r.wipSerie = nil
 | 
						|
		} else {
 | 
						|
			r.wipSerie = wipSerie
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for {
 | 
						|
		// initialize logger
 | 
						|
		r.logger.Info("Running")
 | 
						|
		if r.wipSerie != nil {
 | 
						|
			// if we have a wipSerie
 | 
						|
			l := r.logger.WithField("wipSerie", r.wipSerie)
 | 
						|
			l.Info("Checking wipSerie")
 | 
						|
 | 
						|
			// discard wipSerie not rebased on HEAD
 | 
						|
			// we rebase them at the end of the loop, so this means master advanced without going through the submit queue
 | 
						|
			if !r.gerrit.SerieIsRebasedOnHEAD(r.wipSerie) {
 | 
						|
				l.Warnf("HEAD has moved to {} while still waiting for wipSerie, discarding it", r.gerrit.GetHEAD())
 | 
						|
				r.wipSerie = nil
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// we now need to check CI feedback:
 | 
						|
			// wipSerie might have failed CI in the meantime
 | 
						|
			for _, c := range r.wipSerie.ChangeSets {
 | 
						|
				if c == nil {
 | 
						|
					l.Error("BUG: changeset is nil")
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if c.Verified < 0 {
 | 
						|
					l.WithField("failingChangeset", c).Warnf("wipSerie failed CI in the meantime, discarding.")
 | 
						|
					r.wipSerie = nil
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// it might still be waiting for CI
 | 
						|
			for _, c := range r.wipSerie.ChangeSets {
 | 
						|
				if c == nil {
 | 
						|
					l.Error("BUG: changeset is nil")
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				if c.Verified == 0 {
 | 
						|
					l.WithField("pendingChangeset", c).Warnf("still waiting for CI feedback in wipSerie, going back to sleep.")
 | 
						|
					// break the loop, take a look at it at the next trigger.
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// it might be autosubmittable
 | 
						|
			if r.isAutoSubmittable(r.wipSerie) {
 | 
						|
				l.Infof("submitting wipSerie")
 | 
						|
				// if the WIP changeset is ready (auto submittable and rebased on HEAD), submit
 | 
						|
				for _, changeset := range r.wipSerie.ChangeSets {
 | 
						|
					_, err := r.gerrit.SubmitChangeset(changeset)
 | 
						|
					if err != nil {
 | 
						|
						l.WithField("changeset", changeset).Error("error submitting changeset")
 | 
						|
						r.wipSerie = nil
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
				r.wipSerie = nil
 | 
						|
			} else {
 | 
						|
				l.Error("BUG: wipSerie is not autosubmittable")
 | 
						|
				r.wipSerie = nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		r.logger.Info("Looking for series ready to submit")
 | 
						|
		// Find serie, that:
 | 
						|
		//  * has the auto-submit label
 | 
						|
		//  * has +2 review
 | 
						|
		//  * has +1 CI
 | 
						|
		//  * is rebased on master
 | 
						|
		serie := r.gerrit.FindSerie(func(s *gerrit.Serie) bool {
 | 
						|
			return r.isAutoSubmittable(s) && s.ChangeSets[0].ParentCommitIDs[0] == r.gerrit.GetHEAD()
 | 
						|
		})
 | 
						|
		if serie != nil {
 | 
						|
			r.logger.WithField("serie", serie).Info("Found serie to submit without necessary rebase")
 | 
						|
			r.wipSerie = serie
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Find serie, that:
 | 
						|
		//  * has the auto-submit label
 | 
						|
		//  * has +2 review
 | 
						|
		//  * has +1 CI
 | 
						|
		//  * is NOT rebased on master
 | 
						|
		serie = r.gerrit.FindSerie(r.isAutoSubmittable)
 | 
						|
		if serie == nil {
 | 
						|
			r.logger.Info("no more submittable series found, going back to sleep.")
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		l := r.logger.WithField("serie", serie)
 | 
						|
		l.Info("found serie, which needs a rebase")
 | 
						|
		// TODO: move into Client.RebaseSeries function
 | 
						|
		head := r.gerrit.GetHEAD()
 | 
						|
		for _, changeset := range serie.ChangeSets {
 | 
						|
			changeset, err := r.gerrit.RebaseChangeset(changeset, head)
 | 
						|
			if err != nil {
 | 
						|
				l.Error(err.Error())
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			head = changeset.CommitID
 | 
						|
		}
 | 
						|
		// we don't need to care about updating the rebased changesets or getting the updated HEAD,
 | 
						|
		// as we'll refetch it on the beginning of the next trigger anyways
 | 
						|
		r.wipSerie = serie
 | 
						|
		break
 | 
						|
	}
 | 
						|
 | 
						|
	r.logger.Info("Run complete")
 | 
						|
	return nil
 | 
						|
}
 |