github.com/abayer/test-infra@v0.0.5/prow/cron/cron.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package cron provides a wrapper of robfig/cron, which manages schedule cron jobs for horologium 18 package cron 19 20 import ( 21 "fmt" 22 "strings" 23 "sync" 24 25 "github.com/sirupsen/logrus" 26 "gopkg.in/robfig/cron.v2" // using v2 api, doc at https://godoc.org/gopkg.in/robfig/cron.v2 27 28 "k8s.io/test-infra/prow/config" 29 ) 30 31 // jobStatus is a cache layer for tracking existing cron jobs 32 type jobStatus struct { 33 // entryID is a unique-identifier for each cron entry generated from cronAgent 34 entryID cron.EntryID 35 // triggered marks if a job has been triggered for the next cron.QueuedJobs() call 36 triggered bool 37 // cronStr is a cache for job's cron status 38 // cron entry will be regenerated if cron string changes from the periodic job 39 cronStr string 40 } 41 42 // Cron is a wrapper for cron.Cron 43 type Cron struct { 44 cronAgent *cron.Cron 45 jobs map[string]*jobStatus 46 logger *logrus.Entry 47 lock sync.Mutex 48 } 49 50 // New makes a new Cron object 51 func New() *Cron { 52 return &Cron{ 53 cronAgent: cron.New(), 54 jobs: map[string]*jobStatus{}, 55 logger: logrus.WithField("client", "cron"), 56 } 57 } 58 59 // Start kicks off current cronAgent scheduler 60 func (c *Cron) Start() { 61 c.cronAgent.Start() 62 } 63 64 // Stop pauses current cronAgent scheduler 65 func (c *Cron) Stop() { 66 c.cronAgent.Stop() 67 } 68 69 // QueuedJobs returns a list of jobs that need to be triggered 70 // and reset trigger in jobStatus 71 func (c *Cron) QueuedJobs() []string { 72 c.lock.Lock() 73 defer c.lock.Unlock() 74 75 res := []string{} 76 for k, v := range c.jobs { 77 if v.triggered { 78 res = append(res, k) 79 } 80 c.jobs[k].triggered = false 81 } 82 return res 83 } 84 85 // SyncConfig syncs current cronAgent with current prow config 86 // which add/delete jobs accordingly. 87 func (c *Cron) SyncConfig(cfg *config.Config) error { 88 c.lock.Lock() 89 defer c.lock.Unlock() 90 91 for _, p := range cfg.Periodics { 92 if err := c.addPeriodic(p); err != nil { 93 return err 94 } 95 } 96 97 exist := map[string]bool{} 98 for _, p := range cfg.AllPeriodics() { 99 exist[p.Name] = true 100 } 101 102 for k := range c.jobs { 103 if _, ok := exist[k]; !ok { 104 defer c.removeJob(k) 105 } 106 } 107 108 return nil 109 } 110 111 // HasJob returns if a job has been scheduled in cronAgent or not 112 func (c *Cron) HasJob(name string) bool { 113 c.lock.Lock() 114 defer c.lock.Unlock() 115 116 _, ok := c.jobs[name] 117 return ok 118 } 119 120 func (c *Cron) addPeriodic(p config.Periodic) error { 121 if p.Cron == "" { 122 return nil 123 } 124 125 if job, ok := c.jobs[p.Name]; ok { 126 if job.cronStr == p.Cron { 127 return nil 128 } 129 // job updated, remove old entry 130 if err := c.removeJob(p.Name); err != nil { 131 return err 132 } 133 } 134 135 if err := c.addJob(p.Name, p.Cron); err != nil { 136 return err 137 } 138 139 for _, ras := range p.RunAfterSuccess { 140 if err := c.addPeriodic(ras); err != nil { 141 return err 142 } 143 } 144 145 return nil 146 } 147 148 // addJob adds a cron entry for a job to cronAgent 149 func (c *Cron) addJob(name, cron string) error { 150 id, err := c.cronAgent.AddFunc("TZ=UTC "+cron, func() { 151 c.lock.Lock() 152 defer c.lock.Unlock() 153 154 c.jobs[name].triggered = true 155 c.logger.Infof("Triggering cron job %s.", name) 156 }) 157 158 if err != nil { 159 return fmt.Errorf("cronAgent fails to add job %s with cron %s: %v", name, cron, err) 160 } 161 162 c.jobs[name] = &jobStatus{ 163 entryID: id, 164 cronStr: cron, 165 // try to kick of a periodic trigger right away 166 triggered: strings.HasPrefix(cron, "@every"), 167 } 168 169 c.logger.Infof("Added new cron job %s with trigger %s.", name, cron) 170 return nil 171 } 172 173 // removeJob removes the job from cronAgent 174 func (c *Cron) removeJob(name string) error { 175 job, ok := c.jobs[name] 176 if !ok { 177 return fmt.Errorf("job %s has not been added to cronAgent yet", name) 178 } 179 c.cronAgent.Remove(job.entryID) 180 delete(c.jobs, name) 181 c.logger.Infof("Removed previous cron job %s.", name) 182 return nil 183 }