github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/maintenance.go (about) 1 /* 2 Copyright 2022 Gravitational, Inc. 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 types 18 19 import ( 20 "strings" 21 "time" 22 23 "github.com/gravitational/trace" 24 25 "github.com/gravitational/teleport/api/utils" 26 ) 27 28 const ( 29 // UpgraderKindKuberController is a short name used to identify the kube-controller-based 30 // external upgrader variant. 31 UpgraderKindKubeController = "kube" 32 33 // UpgraderKindSystemdUnit is a short name used to identify the systemd-unit-based 34 // external upgrader variant. 35 UpgraderKindSystemdUnit = "unit" 36 ) 37 38 var validWeekdays = [7]time.Weekday{ 39 time.Sunday, 40 time.Monday, 41 time.Tuesday, 42 time.Wednesday, 43 time.Thursday, 44 time.Friday, 45 time.Saturday, 46 } 47 48 // parseWeekday attempts to interpret a string as a time.Weekday. In the interest of flexibility, 49 // parsing is case-insensitive and supports the common three-letter shorthand accepted by many 50 // common scheduling utilites (e.g. contab, systemd timers). 51 func parseWeekday(s string) (day time.Weekday, ok bool) { 52 for _, w := range validWeekdays { 53 if strings.EqualFold(w.String(), s) || strings.EqualFold(w.String()[:3], s) { 54 return w, true 55 } 56 } 57 58 return time.Sunday, false 59 } 60 61 // generator builds a closure that iterates valid maintenance config from the current day onward. Used in 62 // schedule export logic and tests. 63 func (w *AgentUpgradeWindow) generator(from time.Time) func() (start time.Time, end time.Time) { 64 from = from.UTC() 65 next := time.Date( 66 from.Year(), 67 from.Month(), 68 from.Day(), 69 int(w.UTCStartHour%24), 70 0, // min 71 0, // sec 72 0, // nsec 73 time.UTC, 74 ) 75 76 var weekdays []time.Weekday 77 for _, d := range w.Weekdays { 78 if p, ok := parseWeekday(d); ok { 79 weekdays = append(weekdays, p) 80 } 81 } 82 83 return func() (start time.Time, end time.Time) { 84 for { // safe because invalid weekdays have been filtered out 85 start = next 86 end = start.Add(time.Hour) 87 88 next = next.AddDate(0, 0, 1) 89 90 if len(weekdays) == 0 { 91 return 92 } 93 94 for _, day := range weekdays { 95 if start.Weekday() == day { 96 return 97 } 98 } 99 } 100 } 101 } 102 103 // Export exports the next `n` upgrade windows as a schedule object, starting from `from`. 104 func (w *AgentUpgradeWindow) Export(from time.Time, n int) AgentUpgradeSchedule { 105 gen := w.generator(from) 106 107 sched := AgentUpgradeSchedule{ 108 Windows: make([]ScheduledAgentUpgradeWindow, 0, n), 109 } 110 for i := 0; i < n; i++ { 111 start, stop := gen() 112 sched.Windows = append(sched.Windows, ScheduledAgentUpgradeWindow{ 113 Start: start.UTC(), 114 Stop: stop.UTC(), 115 }) 116 } 117 118 return sched 119 } 120 121 func (s *AgentUpgradeSchedule) Clone() *AgentUpgradeSchedule { 122 return utils.CloneProtoMsg(s) 123 } 124 125 // NewClusterMaintenanceConfig creates a new maintenance config with no parameters set. 126 func NewClusterMaintenanceConfig() ClusterMaintenanceConfig { 127 var cmc ClusterMaintenanceConfigV1 128 cmc.setStaticFields() 129 return &cmc 130 } 131 132 // ClusterMaintenanceConfig represents a singleton config object used to schedule maintenance 133 // windows. Currently this config object's only purpose is to configure a global agent 134 // upgrade window, used to coordinate upgrade timing for non-control-plane agents. 135 type ClusterMaintenanceConfig interface { 136 Resource 137 138 // GetNonce gets the nonce of the maintenance config. 139 GetNonce() uint64 140 141 // WithNonce creates a shallow copy with a new nonce. 142 WithNonce(nonce uint64) any 143 144 // GetAgentUpgradeWindow gets the agent upgrade window. 145 GetAgentUpgradeWindow() (win AgentUpgradeWindow, ok bool) 146 147 // SetAgentUpgradeWindow sets the agent upgrade window. 148 SetAgentUpgradeWindow(win AgentUpgradeWindow) 149 150 // WithinUpgradeWindow returns true if the time is within the configured 151 // upgrade window. 152 WithinUpgradeWindow(t time.Time) bool 153 154 CheckAndSetDefaults() error 155 } 156 157 func (m *ClusterMaintenanceConfigV1) setStaticFields() { 158 if m.Version == "" { 159 m.Version = V1 160 } 161 162 if m.Kind == "" { 163 m.Kind = KindClusterMaintenanceConfig 164 } 165 166 if m.Metadata.Name == "" { 167 m.Metadata.Name = MetaNameClusterMaintenanceConfig 168 } 169 } 170 171 func (m *ClusterMaintenanceConfigV1) CheckAndSetDefaults() error { 172 m.setStaticFields() 173 174 if err := m.ResourceHeader.CheckAndSetDefaults(); err != nil { 175 return trace.Wrap(err) 176 } 177 178 if m.Version != V1 { 179 return trace.BadParameter("unexpected maintenance config resource version %q (expected %q)", m.Version, V1) 180 } 181 182 if m.Kind == MetaNameClusterMaintenanceConfig { 183 // normalize easy mixup 184 m.Kind = KindClusterMaintenanceConfig 185 } 186 187 if m.Kind != KindClusterMaintenanceConfig { 188 return trace.BadParameter("unexpected maintenance config kind %q (expected %q)", m.Kind, KindClusterMaintenanceConfig) 189 } 190 191 if m.Metadata.Name == KindClusterMaintenanceConfig { 192 // normalize easy mixup 193 m.Metadata.Name = MetaNameClusterMaintenanceConfig 194 } 195 196 if m.Metadata.Name != MetaNameClusterMaintenanceConfig { 197 return trace.BadParameter("unexpected maintenance config name %q (expected %q)", m.Metadata.Name, MetaNameClusterMaintenanceConfig) 198 } 199 200 if m.Spec.AgentUpgrades != nil { 201 if h := m.Spec.AgentUpgrades.UTCStartHour; h > 23 { 202 return trace.BadParameter("agent upgrade window utc start hour must be in range 0..23, got %d", h) 203 } 204 205 for _, day := range m.Spec.AgentUpgrades.Weekdays { 206 if _, ok := parseWeekday(day); !ok { 207 return trace.BadParameter("invalid weekday in agent upgrade window: %q", day) 208 } 209 } 210 } 211 212 return nil 213 } 214 215 func (m *ClusterMaintenanceConfigV1) GetNonce() uint64 { 216 return m.Nonce 217 } 218 219 func (m *ClusterMaintenanceConfigV1) WithNonce(nonce uint64) any { 220 shallowCopy := *m 221 shallowCopy.Nonce = nonce 222 return &shallowCopy 223 } 224 225 func (m *ClusterMaintenanceConfigV1) GetAgentUpgradeWindow() (win AgentUpgradeWindow, ok bool) { 226 if m.Spec.AgentUpgrades == nil { 227 return AgentUpgradeWindow{}, false 228 } 229 230 return *m.Spec.AgentUpgrades, true 231 } 232 233 func (m *ClusterMaintenanceConfigV1) SetAgentUpgradeWindow(win AgentUpgradeWindow) { 234 m.Spec.AgentUpgrades = &win 235 } 236 237 // WithinUpgradeWindow returns true if the time is within the configured 238 // upgrade window. 239 func (m *ClusterMaintenanceConfigV1) WithinUpgradeWindow(t time.Time) bool { 240 upgradeWindow, ok := m.GetAgentUpgradeWindow() 241 if !ok { 242 return false 243 } 244 245 if len(upgradeWindow.Weekdays) == 0 { 246 if int(upgradeWindow.UTCStartHour) == t.Hour() { 247 return true 248 } 249 } 250 251 weekday := t.Weekday().String() 252 for _, upgradeWeekday := range upgradeWindow.Weekdays { 253 if weekday == upgradeWeekday { 254 if int(upgradeWindow.UTCStartHour) == t.Hour() { 255 return true 256 } 257 } 258 } 259 return false 260 }