github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/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  }