github.com/cilium/cilium@v1.16.2/pkg/controller/manager.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package controller 5 6 import ( 7 "context" 8 "fmt" 9 10 "github.com/go-openapi/strfmt" 11 "github.com/google/uuid" 12 13 "github.com/cilium/cilium/api/v1/models" 14 "github.com/cilium/cilium/pkg/lock" 15 "github.com/cilium/cilium/pkg/option" 16 "github.com/cilium/cilium/pkg/time" 17 ) 18 19 var ( 20 // globalStatus is the global status of all controllers 21 globalStatus = NewManager() 22 ) 23 24 type controllerMap map[string]*managedController 25 26 // Manager is a list of controllers 27 type Manager struct { 28 controllers controllerMap 29 mutex lock.RWMutex 30 } 31 32 // NewManager allocates a new manager 33 func NewManager() *Manager { 34 return &Manager{ 35 controllers: controllerMap{}, 36 } 37 } 38 39 // GetGlobalStatus returns the status of all controllers 40 func GetGlobalStatus() models.ControllerStatuses { 41 return globalStatus.GetStatusModel() 42 } 43 44 // UpdateController installs or updates a controller in the 45 // manager. A controller is primarily identified by its name. 46 // If a controller with the name already exists, the controller 47 // will be shut down and replaced with the provided controller. 48 // 49 // Updating a controller will cause the DoFunc to be run immediately regardless 50 // of any previous conditions. It will also cause any statistics to be reset. 51 func (m *Manager) UpdateController(name string, params ControllerParams) { 52 m.updateController(name, params) 53 } 54 55 func (m *Manager) updateController(name string, params ControllerParams) *managedController { 56 start := time.Now() 57 58 m.mutex.Lock() 59 defer m.mutex.Unlock() 60 61 if m.controllers == nil { 62 m.controllers = controllerMap{} 63 } 64 65 if params.Group.Name == "" { 66 log.Errorf( 67 "Controller initialized with unpopulated group information. " + 68 "Metrics will not be exported for this controller.") 69 } 70 71 ctrl := m.lookupLocked(name) 72 if ctrl != nil { 73 ctrl.getLogger().Debug("Updating existing controller") 74 ctrl.updateParamsLocked(params) 75 76 // Notify the goroutine of the params update. 77 select { 78 case ctrl.update <- ctrl.params: 79 default: 80 } 81 82 ctrl.getLogger().Debug("Controller update time: ", time.Since(start)) 83 } else { 84 return m.createControllerLocked(name, params) 85 } 86 87 return ctrl 88 } 89 90 func (m *Manager) createControllerLocked(name string, params ControllerParams) *managedController { 91 ctrl := &managedController{ 92 controller: controller{ 93 name: name, 94 group: params.Group, 95 uuid: uuid.New().String(), 96 stop: make(chan struct{}), 97 update: make(chan ControllerParams, 1), 98 trigger: make(chan struct{}, 1), 99 terminated: make(chan struct{}), 100 }, 101 } 102 ctrl.updateParamsLocked(params) 103 ctrl.getLogger().Debug("Starting new controller") 104 105 m.controllers[ctrl.name] = ctrl 106 107 globalStatus.mutex.Lock() 108 globalStatus.controllers[ctrl.uuid] = ctrl 109 globalStatus.mutex.Unlock() 110 111 go ctrl.runController(ctrl.params) 112 return ctrl 113 } 114 115 // CreateController installs a new controller in the 116 // manager. If a controller with the name already exists 117 // this method returns false without triggering, otherwise 118 // creates the controller and runs it immediately. 119 func (m *Manager) CreateController(name string, params ControllerParams) bool { 120 m.mutex.Lock() 121 defer m.mutex.Unlock() 122 123 if m.controllers != nil { 124 if ctrl := m.lookupLocked(name); ctrl != nil { 125 return false 126 } 127 } else { 128 m.controllers = controllerMap{} 129 } 130 m.createControllerLocked(name, params) 131 return true 132 } 133 134 func (m *Manager) removeController(ctrl *managedController) { 135 ctrl.stopController() 136 delete(m.controllers, ctrl.name) 137 138 globalStatus.mutex.Lock() 139 delete(globalStatus.controllers, ctrl.uuid) 140 globalStatus.mutex.Unlock() 141 142 ctrl.getLogger().Debug("Removed controller") 143 } 144 145 func (m *Manager) lookup(name string) *managedController { 146 m.mutex.RLock() 147 defer m.mutex.RUnlock() 148 return m.lookupLocked(name) 149 } 150 151 func (m *Manager) lookupLocked(name string) *managedController { 152 if c, ok := m.controllers[name]; ok { 153 return c 154 } 155 return nil 156 } 157 158 func (m *Manager) removeAndReturnController(name string) (*managedController, error) { 159 m.mutex.Lock() 160 defer m.mutex.Unlock() 161 162 if m.controllers == nil { 163 return nil, fmt.Errorf("empty controller map") 164 } 165 166 oldCtrl := m.lookupLocked(name) 167 if oldCtrl == nil { 168 return nil, fmt.Errorf("unable to find controller %s", name) 169 } 170 171 m.removeController(oldCtrl) 172 173 return oldCtrl, nil 174 } 175 176 // RemoveController stops and removes a controller from the manager. If DoFunc 177 // is currently running, DoFunc is allowed to complete in the background. 178 func (m *Manager) RemoveController(name string) error { 179 _, err := m.removeAndReturnController(name) 180 return err 181 } 182 183 // RemoveControllerAndWait stops and removes a controller using 184 // RemoveController() and then waits for it to run to completion. 185 func (m *Manager) RemoveControllerAndWait(name string) error { 186 oldCtrl, err := m.removeAndReturnController(name) 187 if err == nil { 188 <-oldCtrl.terminated 189 } 190 191 return err 192 } 193 194 func (m *Manager) removeAll() []*managedController { 195 ctrls := []*managedController{} 196 197 m.mutex.Lock() 198 defer m.mutex.Unlock() 199 200 if m.controllers == nil { 201 return ctrls 202 } 203 204 for _, ctrl := range m.controllers { 205 m.removeController(ctrl) 206 ctrls = append(ctrls, ctrl) 207 } 208 209 return ctrls 210 } 211 212 // RemoveAll stops and removes all controllers of the manager 213 func (m *Manager) RemoveAll() { 214 m.removeAll() 215 } 216 217 // RemoveAllAndWait stops and removes all controllers of the manager and then 218 // waits for all controllers to exit 219 func (m *Manager) RemoveAllAndWait() { 220 ctrls := m.removeAll() 221 for _, ctrl := range ctrls { 222 <-ctrl.terminated 223 } 224 } 225 226 // GetStatusModel returns the status of all controllers as models.ControllerStatuses 227 func (m *Manager) GetStatusModel() models.ControllerStatuses { 228 // Create a copy of pointers to current controller so we can unlock the 229 // manager mutex quickly again 230 controllers := controllerMap{} 231 m.mutex.RLock() 232 for key, c := range m.controllers { 233 controllers[key] = c 234 } 235 m.mutex.RUnlock() 236 237 statuses := models.ControllerStatuses{} 238 for _, c := range controllers { 239 statuses = append(statuses, c.GetStatusModel()) 240 } 241 242 return statuses 243 } 244 245 // TriggerController triggers the controller with the specified name. 246 func (m *Manager) TriggerController(name string) { 247 ctrl := m.lookup(name) 248 if ctrl == nil { 249 return 250 } 251 252 select { 253 case ctrl.trigger <- struct{}{}: 254 default: 255 } 256 } 257 258 type managedController struct { 259 controller 260 261 params ControllerParams 262 cancelDoFunc context.CancelFunc 263 } 264 265 // updateParamsLocked sanitizes and sets the controller's parameters. 266 // 267 // If the RunInterval exceeds ControllerMaxInterval, it will be capped. 268 // 269 // Manager's mutex must be held 270 func (c *managedController) updateParamsLocked(params ControllerParams) { 271 // ensure the callbacks are valid 272 if params.DoFunc == nil { 273 params.DoFunc = func(ctx context.Context) error { 274 return undefinedDoFunc(c.name) 275 } 276 } 277 if params.StopFunc == nil { 278 params.StopFunc = NoopFunc 279 } 280 281 // Enforce max controller interval 282 maxInterval := time.Duration(option.Config.MaxControllerInterval) * time.Second 283 if maxInterval > 0 && params.RunInterval > maxInterval { 284 c.getLogger().Infof("Limiting interval to %s", maxInterval) 285 params.RunInterval = maxInterval 286 } 287 288 // Save current context on update if not canceling 289 ctx := c.params.Context 290 // Check if the current context needs to be cancelled 291 if c.params.CancelDoFuncOnUpdate && c.cancelDoFunc != nil { 292 c.cancelDoFunc() 293 c.params.Context = nil 294 } 295 296 // (re)set the context as the previous might have been cancelled 297 if c.params.Context == nil { 298 if params.Context == nil { 299 ctx, c.cancelDoFunc = context.WithCancel(context.Background()) 300 } else { 301 ctx, c.cancelDoFunc = context.WithCancel(params.Context) 302 } 303 } 304 305 c.params = params 306 c.params.Context = ctx 307 } 308 309 func (c *managedController) stopController() { 310 if c.cancelDoFunc != nil { 311 c.cancelDoFunc() 312 } 313 314 close(c.stop) 315 } 316 317 // GetStatusModel returns a models.ControllerStatus representing the 318 // controller's configuration & status 319 func (c *managedController) GetStatusModel() *models.ControllerStatus { 320 c.mutex.RLock() 321 defer c.mutex.RUnlock() 322 323 status := &models.ControllerStatus{ 324 Name: c.name, 325 UUID: strfmt.UUID(c.uuid), 326 Configuration: &models.ControllerStatusConfiguration{ 327 ErrorRetry: !c.params.NoErrorRetry, 328 ErrorRetryBase: strfmt.Duration(c.params.ErrorRetryBaseDuration), 329 Interval: strfmt.Duration(c.params.RunInterval), 330 }, 331 Status: &models.ControllerStatusStatus{ 332 SuccessCount: int64(c.successCount), 333 LastSuccessTimestamp: strfmt.DateTime(c.lastSuccessStamp), 334 FailureCount: int64(c.failureCount), 335 LastFailureTimestamp: strfmt.DateTime(c.lastErrorStamp), 336 ConsecutiveFailureCount: int64(c.consecutiveErrors), 337 }, 338 } 339 340 if c.lastError != nil { 341 status.Status.LastFailureMsg = c.lastError.Error() 342 } 343 344 return status 345 }