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 }