go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/api/config/v1/amount.go (about) 1 // Copyright 2019 The LUCI 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "sort" 19 "time" 20 21 "go.chromium.org/luci/common/errors" 22 "go.chromium.org/luci/config/validation" 23 ) 24 25 // getAmount returns the amount to use given the proposed amount and time. 26 // Assumes this amount has been validated. 27 func (a *Amount) getAmount(proposed int32, now time.Time) (int32, error) { 28 for _, s := range a.GetChange() { 29 start, err := s.mostRecentStart(now) 30 if err != nil { 31 return 0, errors.Annotate(err, "invalid start time").Err() 32 } 33 if start.Before(now) || start.Equal(now) { 34 sec, err := s.Length.ToSeconds() 35 if err != nil { 36 return 0, errors.Annotate(err, "invalid length").Err() 37 } 38 end := start.Add(time.Second * time.Duration(sec)) 39 if now.Before(end) { 40 if proposed < s.Min { 41 return s.Min, nil 42 } 43 if proposed > s.Max { 44 return s.Max, nil 45 } 46 return proposed, nil 47 } 48 } 49 } 50 if proposed < a.GetMin() { 51 return a.GetMin(), nil 52 } 53 if proposed > a.GetMax() { 54 return a.GetMax(), nil 55 } 56 return proposed, nil 57 } 58 59 // Validate validates this amount. 60 func (a *Amount) Validate(c *validation.Context) { 61 if a.GetMin() < 0 { 62 c.Errorf("minimum amount must be non-negative") 63 } 64 if a.GetMax() < 0 { 65 c.Errorf("maximum amount must be non-negative") 66 } 67 if a.GetMin() > a.GetMax() { 68 c.Errorf("minimum amount must not exceed maximum amount") 69 } 70 a.validateSchedules(c) 71 } 72 73 // indexedSchedule encapsulates *Schedules for sorting. 74 type indexedSchedule struct { 75 *Schedule 76 // index is the original index of the *Schedule before sorting. 77 index int 78 // sortKey is the time.Time by which []indexedSchedules should be sorted. 79 sortKey time.Time 80 } 81 82 // validateSchedules validates the schedules in this amount. 83 func (a *Amount) validateSchedules(c *validation.Context) { 84 // The algorithm works with any time 7 days greater than the zero time. 85 now := time.Date(2018, 1, 1, 12, 0, 0, 0, time.UTC) 86 schs := make([]indexedSchedule, len(a.GetChange())) 87 for i, s := range a.GetChange() { 88 c.Enter("change %d", i) 89 s.Validate(c) 90 c.Exit() 91 t, err := s.mostRecentStart(now) 92 if err != nil { 93 // s.Validate(c) already emitted this error. 94 return 95 } 96 schs[i] = indexedSchedule{ 97 Schedule: s, 98 index: i, 99 sortKey: t, 100 } 101 } 102 sort.Slice(schs, func(i, j int) bool { return schs[i].sortKey.Before(schs[j].sortKey) }) 103 prevEnd := time.Time{} 104 for i := 0; i < len(schs); i++ { 105 c.Enter("change %d", schs[i].index) 106 start := schs[i].sortKey 107 if schs[i].sortKey.Before(prevEnd) { 108 // Implies intervals are half-open: [start, end). prevEnd 109 // is initialized to the zero time, therefore this can't 110 // succeed when i is 0, meaning i-1 is never -1. 111 c.Errorf("start time is before change %d", schs[i-1].index) 112 } 113 sec, err := schs[i].Schedule.Length.ToSeconds() 114 if err != nil { 115 c.Exit() 116 return 117 } 118 prevEnd = start.Add(time.Second * time.Duration(sec)) 119 c.Exit() 120 } 121 if len(schs) > 0 { 122 // Schedules are relative to the week. 123 // Check for a conflict between the last and first. 124 c.Enter("change %d", schs[0].index) 125 // Treat the first schedule as starting in a week. This checks for a conflict 126 // between the last configured schedule and the first configured schedule. 127 start := schs[0].sortKey.Add(time.Hour * time.Duration(24*7)) 128 if start.Before(prevEnd) { 129 c.Errorf("start time is before change %d", schs[len(schs)-1].index) 130 } 131 } 132 }