...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/scheduler/scheduler.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/scheduler

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package scheduler
    15  
    16  import (
    17  	"fmt"
    18  	"time"
    19  
    20  	"github.com/robfig/cron/v3"
    21  
    22  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    23  )
    24  
    25  const (
    26  	// Set the top bit if a star was included in the expression.
    27  	starBit = 1 << 63
    28  )
    29  
    30  // LastTime returns the last time this schedule activated, less than or equal with the given time.
    31  func LastTime(spec v1alpha1.SchedulerSpec, now time.Time) (*time.Time, error) {
    32  	scheduler, err := cron.ParseStandard(spec.Cron)
    33  	if err != nil {
    34  		return nil, fmt.Errorf("fail to parse runner rule %s, %v", spec.Cron, err)
    35  	}
    36  	var last time.Time
    37  	if cronSpec, ok := scheduler.(*cron.SpecSchedule); ok {
    38  		scheduleLast := &cusSchedule{cronSpec}
    39  		last = scheduleLast.Last(now)
    40  	} else if cronSpec, ok := scheduler.(cron.ConstantDelaySchedule); ok {
    41  		scheduleLast := &cusConstantDelaySchedule{cronSpec}
    42  		last = scheduleLast.Last(now)
    43  	} else {
    44  		return nil, fmt.Errorf("assert cron spec failed")
    45  	}
    46  	return &last, nil
    47  }
    48  
    49  type cusConstantDelaySchedule struct {
    50  	cron.ConstantDelaySchedule
    51  }
    52  
    53  // Last returns the last time this schedule activated, less than or equal with the given time.
    54  // So it would always return now
    55  func (s cusConstantDelaySchedule) Last(t time.Time) time.Time {
    56  	return t
    57  }
    58  
    59  type cusSchedule struct {
    60  	*cron.SpecSchedule
    61  }
    62  
    63  // Last returns the last time this schedule activated, less than or equal with the given time.
    64  // If no time can be found to satisfy the schedule, return the zero time.
    65  // Modified from the original `Next` function in robfig/cron at Dec 15, 2020
    66  func (s *cusSchedule) Last(t time.Time) time.Time {
    67  	// General approach:
    68  	// For Month, Day, Hour, Minute, Second:
    69  	// Check if the time value matches.  If yes, continue to the next field.
    70  	// If the field doesn't match the schedule, then decrement the field until it matches.
    71  	// While decrementing the field, a wrap-around brings it back to the beginning
    72  	// of the field list (since it is necessary to re-verify previous field
    73  	// values)
    74  
    75  	// Convert the given time into the schedule's timezone, if one is specified.
    76  	// Save the original timezone so we can convert back after we find a time.
    77  	// Note that schedules without a time zone specified (time.Local) are treated
    78  	// as local to the time provided.
    79  	origLocation := t.Location()
    80  	loc := s.Location
    81  	if loc == time.Local {
    82  		loc = t.Location()
    83  	}
    84  	if s.Location != time.Local {
    85  		t = t.In(s.Location)
    86  	}
    87  
    88  	// If no time is found within five years, return zero.
    89  	yearLimit := t.Year() - 5
    90  
    91  WRAP:
    92  	if t.Year() < yearLimit {
    93  		return time.Time{}
    94  	}
    95  
    96  	// Find the first applicable month.
    97  	// If it's this month, then do nothing.
    98  	for 1<<uint(t.Month())&s.Month == 0 {
    99  		t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc).Add(-1 * time.Second)
   100  		if t.Month() == time.December {
   101  			goto WRAP
   102  		}
   103  	}
   104  
   105  	// Now get a day in that month.
   106  	for !dayMatches(s, t) {
   107  		finalDay := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc).Add(-1 * time.Second).Day()
   108  		t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc).Add(-1 * time.Second)
   109  		if t.Day() == finalDay {
   110  			goto WRAP
   111  		}
   112  	}
   113  
   114  	for 1<<uint(t.Hour())&s.Hour == 0 {
   115  		t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc).Add(-1 * time.Second)
   116  		if t.Hour() == 23 {
   117  			goto WRAP
   118  		}
   119  	}
   120  
   121  	for 1<<uint(t.Minute())&s.Minute == 0 {
   122  		t = t.Truncate(time.Minute).Add(-1 * time.Second)
   123  		if t.Minute() == 59 {
   124  			goto WRAP
   125  		}
   126  	}
   127  
   128  	for 1<<uint(t.Second())&s.Second == 0 {
   129  		t = t.Add(-1 * time.Second)
   130  		if t.Second() == 59 {
   131  			goto WRAP
   132  		}
   133  	}
   134  
   135  	return t.In(origLocation)
   136  }
   137  
   138  // dayMatches returns true if the schedule's day-of-week and day-of-month
   139  // restrictions are satisfied by the given time.
   140  func dayMatches(s *cusSchedule, t time.Time) bool {
   141  	var (
   142  		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
   143  		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
   144  	)
   145  	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
   146  		return domMatch && dowMatch
   147  	}
   148  	return domMatch || dowMatch
   149  }
   150