github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/runner/main/runner.go (about)

     1  // Main package for the Evergreen runner.
     2  package main
     3  
     4  import (
     5  	"bytes"
     6  	"flag"
     7  	"fmt"
     8  	"os"
     9  	"os/signal"
    10  	"sync"
    11  	"syscall"
    12  	"text/template"
    13  	"time"
    14  
    15  	"github.com/evergreen-ci/evergreen"
    16  	"github.com/evergreen-ci/evergreen/db"
    17  	"github.com/evergreen-ci/evergreen/notify"
    18  	_ "github.com/evergreen-ci/evergreen/plugin/config"
    19  	. "github.com/evergreen-ci/evergreen/runner"
    20  	"github.com/evergreen-ci/evergreen/util"
    21  	"github.com/mongodb/grip"
    22  	"github.com/mongodb/grip/level"
    23  	"github.com/mongodb/grip/message"
    24  	"github.com/mongodb/grip/send"
    25  	"github.com/pkg/errors"
    26  )
    27  
    28  func init() {
    29  	usageTemplate := template.Must(template.New("usage").Parse(
    30  		`{{.Program}} runs the Evergreen processes at regular intervals.
    31  
    32  Usage:
    33    {{.Program}} [flags] [process name]
    34  
    35  Supported flags are:
    36  {{.FlagDefaults}}
    37  Supported proccesses are:{{range .Runners}}
    38    {{.Name}}: {{.Description}}{{end}}
    39  
    40  Pass a single process name to run that process once,
    41  or leave [process name] blank to run all processes
    42  at regular intervals.
    43  `))
    44  
    45  	flag.Usage = func() {
    46  		// capture the default flag output for use in the template
    47  		flagDefaults := &bytes.Buffer{}
    48  		flag.CommandLine.SetOutput(flagDefaults)
    49  		flag.CommandLine.PrintDefaults()
    50  
    51  		// execute the usage template
    52  		usageTemplate.Execute(os.Stderr, struct {
    53  			Program      string
    54  			FlagDefaults string
    55  			Runners      []ProcessRunner
    56  		}{os.Args[0], flagDefaults.String(), Runners})
    57  	}
    58  }
    59  
    60  var (
    61  	runInterval = int64(30)
    62  )
    63  
    64  func main() {
    65  	settings := evergreen.GetSettingsOrExit()
    66  	if settings.Runner.LogFile != "" {
    67  		sender, err := send.MakeFileLogger(settings.Runner.LogFile)
    68  		grip.CatchEmergencyFatal(err)
    69  		defer sender.Close()
    70  		grip.CatchEmergencyFatal(grip.SetSender(sender))
    71  	} else {
    72  		sender := send.MakeNative()
    73  		defer sender.Close()
    74  		grip.CatchEmergencyFatal(grip.SetSender(sender))
    75  	}
    76  	evergreen.SetLegacyLogger()
    77  	grip.SetName("evg-runner")
    78  	grip.SetDefaultLevel(level.Info)
    79  	grip.SetThreshold(level.Debug)
    80  	grip.Notice(message.Fields{"build": evergreen.BuildRevision, "process": grip.Name()})
    81  
    82  	home := evergreen.FindEvergreenHome()
    83  	if home == "" {
    84  		grip.EmergencyFatal("EVGHOME environment variable must be set to execute runner")
    85  	}
    86  
    87  	defer util.RecoverAndLogStackTrace()
    88  
    89  	db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(settings))
    90  
    91  	// just run one process if an argument was passed in
    92  	if flag.Arg(0) != "" {
    93  		grip.CatchEmergencyFatal(runProcessByName(flag.Arg(0), settings))
    94  	}
    95  
    96  	if settings.Runner.IntervalSeconds <= 0 {
    97  		grip.Warningf("Interval set to %v (<= 0s) using %v instead",
    98  			settings.Runner.IntervalSeconds, runInterval)
    99  	} else {
   100  		runInterval = settings.Runner.IntervalSeconds
   101  	}
   102  
   103  	// start and schedule runners
   104  	wg := &sync.WaitGroup{}
   105  	ch := startRunners(wg, settings)
   106  	go listenForSIGTERM(ch)
   107  
   108  	// wait for all the processes to exit
   109  	wg.Wait()
   110  	grip.Infof("Cleanly terminated all %d processes", len(Runners))
   111  }
   112  
   113  // startRunners starts a goroutine for each runner exposed via Runners. It
   114  // returns a channel on which all runners listen on, for when to terminate.
   115  func startRunners(wg *sync.WaitGroup, s *evergreen.Settings) chan bool {
   116  	c := make(chan bool)
   117  	duration := time.Duration(runInterval) * time.Second
   118  
   119  	for _, r := range Runners {
   120  		wg.Add(1)
   121  
   122  		// start each runner in its own goroutine
   123  		go func(r ProcessRunner, s *evergreen.Settings, terminateChan chan bool) {
   124  			defer wg.Done()
   125  
   126  			grip.Infoln("Starting runner process:", r.Name())
   127  
   128  			loop := true
   129  
   130  			for loop {
   131  				if err := r.Run(s); err != nil {
   132  					subject := fmt.Sprintf("%v failure", r.Name())
   133  					grip.Error(err)
   134  					if err = notify.NotifyAdmins(subject, err.Error(), s); err != nil {
   135  						grip.Errorln("sending email: %+v", err)
   136  					}
   137  				}
   138  				select {
   139  				case <-time.NewTimer(duration).C:
   140  				case loop = <-terminateChan:
   141  				}
   142  			}
   143  			grip.Infoln("Cleanly terminated runner process:", r.Name())
   144  		}(r, s, c)
   145  	}
   146  	return c
   147  }
   148  
   149  // listenForSIGTERM listens for the SIGTERM signal and closes the
   150  // channel on which each runner is listening as soon as the signal
   151  // is received.
   152  func listenForSIGTERM(ch chan bool) {
   153  	sigChan := make(chan os.Signal)
   154  	// notify us when SIGTERM is received
   155  	signal.Notify(sigChan, syscall.SIGTERM)
   156  	<-sigChan
   157  	grip.Infof("Terminating %d processes", len(Runners))
   158  	close(ch)
   159  }
   160  
   161  // runProcessByName runs a single process given its name and evergreen Settings.
   162  // Returns an error if the process does not exist.
   163  func runProcessByName(name string, settings *evergreen.Settings) error {
   164  	for _, r := range Runners {
   165  		if r.Name() == name {
   166  			grip.Infof("Running standalone %s process", name)
   167  			if err := r.Run(settings); err != nil {
   168  				grip.Error(err)
   169  			}
   170  			return nil
   171  		}
   172  	}
   173  	return errors.Errorf("process '%s' does not exist", name)
   174  }