istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/mesh/watcher.go (about) 1 // Copyright Istio 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 mesh 16 17 import ( 18 "reflect" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 meshconfig "istio.io/api/mesh/v1alpha1" 24 "istio.io/istio/pkg/filewatcher" 25 "istio.io/istio/pkg/log" 26 "istio.io/istio/pkg/slices" 27 "istio.io/istio/pkg/util/protomarshal" 28 ) 29 30 // Holder of a mesh configuration. 31 type Holder interface { 32 Mesh() *meshconfig.MeshConfig 33 } 34 35 // Watcher is a Holder whose mesh config can be updated asynchronously. 36 type Watcher interface { 37 Holder 38 39 // AddMeshHandler registers a callback handler for changes to the mesh config. 40 AddMeshHandler(h func()) *WatcherHandlerRegistration 41 42 // DeleteMeshHandler unregisters a callback handler when remote cluster is removed. 43 DeleteMeshHandler(registration *WatcherHandlerRegistration) 44 45 // HandleUserMeshConfig keeps track of user mesh config overrides. These are merged with the standard 46 // mesh config, which takes precedence. 47 HandleUserMeshConfig(string) 48 } 49 50 // MultiWatcher is a struct wrapping the internal injector to let users know that both 51 type MultiWatcher struct { 52 *internalWatcher 53 internalNetworkWatcher 54 } 55 56 func NewMultiWatcher(config *meshconfig.MeshConfig) *MultiWatcher { 57 iw := &internalWatcher{} 58 iw.MeshConfig.Store(config) 59 return &MultiWatcher{ 60 internalWatcher: iw, 61 } 62 } 63 64 var _ Watcher = &internalWatcher{} 65 66 type internalWatcher struct { 67 mutex sync.Mutex 68 handlers []*WatcherHandlerRegistration 69 // Current merged mesh config 70 MeshConfig atomic.Pointer[meshconfig.MeshConfig] 71 72 userMeshConfig string 73 revMeshConfig string 74 } 75 76 // NewFixedWatcher creates a new Watcher that always returns the given mesh config. It will never 77 // fire any events, since the config never changes. 78 func NewFixedWatcher(mesh *meshconfig.MeshConfig) Watcher { 79 iw := internalWatcher{} 80 iw.MeshConfig.Store(mesh) 81 return &iw 82 } 83 84 // NewFileWatcher creates a new Watcher for changes to the given mesh config file. Returns an error 85 // if the given file does not exist or failed during parsing. 86 func NewFileWatcher(fileWatcher filewatcher.FileWatcher, filename string, multiWatch bool) (Watcher, error) { 87 meshConfigYaml, err := ReadMeshConfigData(filename) 88 if err != nil { 89 return nil, err 90 } 91 92 meshConfig, err := ApplyMeshConfigDefaults(meshConfigYaml) 93 if err != nil { 94 return nil, err 95 } 96 97 w := &internalWatcher{ 98 revMeshConfig: meshConfigYaml, 99 } 100 w.MeshConfig.Store(meshConfig) 101 102 // Watch the config file for changes and reload if it got modified 103 addFileWatcher(fileWatcher, filename, func() { 104 if multiWatch { 105 meshConfig, err := ReadMeshConfigData(filename) 106 if err != nil { 107 log.Warnf("failed to read mesh configuration, using default: %v", err) 108 return 109 } 110 w.HandleMeshConfigData(meshConfig) 111 return 112 } 113 // Reload the config file 114 meshConfig, err = ReadMeshConfig(filename) 115 if err != nil { 116 log.Warnf("failed to read mesh configuration, using default: %v", err) 117 return 118 } 119 w.HandleMeshConfig(meshConfig) 120 }) 121 return w, nil 122 } 123 124 // Mesh returns the latest mesh config. 125 func (w *internalWatcher) Mesh() *meshconfig.MeshConfig { 126 return w.MeshConfig.Load() 127 } 128 129 // AddMeshHandler registers a callback handler for changes to the mesh config. 130 func (w *internalWatcher) AddMeshHandler(h func()) *WatcherHandlerRegistration { 131 w.mutex.Lock() 132 defer w.mutex.Unlock() 133 134 handler := &WatcherHandlerRegistration{ 135 handler: h, 136 } 137 w.handlers = append(w.handlers, handler) 138 return handler 139 } 140 141 func (w *internalWatcher) DeleteMeshHandler(registration *WatcherHandlerRegistration) { 142 w.mutex.Lock() 143 defer w.mutex.Unlock() 144 145 if len(w.handlers) == 0 { 146 return 147 } 148 149 w.handlers = slices.FilterInPlace(w.handlers, func(handler *WatcherHandlerRegistration) bool { 150 return handler != registration 151 }) 152 } 153 154 // HandleMeshConfigData keeps track of the standard mesh config. These are merged with the user 155 // mesh config, but takes precedence. 156 func (w *internalWatcher) HandleMeshConfigData(yaml string) { 157 w.mutex.Lock() 158 defer w.mutex.Unlock() 159 w.revMeshConfig = yaml 160 merged := w.merged() 161 w.handleMeshConfigInternal(merged) 162 } 163 164 // HandleUserMeshConfig keeps track of user mesh config overrides. These are merged with the standard 165 // mesh config, which takes precedence. 166 func (w *internalWatcher) HandleUserMeshConfig(yaml string) { 167 w.mutex.Lock() 168 defer w.mutex.Unlock() 169 w.userMeshConfig = yaml 170 merged := w.merged() 171 w.handleMeshConfigInternal(merged) 172 } 173 174 // merged returns the merged user and revision config. 175 func (w *internalWatcher) merged() *meshconfig.MeshConfig { 176 mc := DefaultMeshConfig() 177 if w.userMeshConfig != "" { 178 mc1, err := ApplyMeshConfig(w.userMeshConfig, mc) 179 if err != nil { 180 log.Errorf("user config invalid, ignoring it %v %s", err, w.userMeshConfig) 181 } else { 182 mc = mc1 183 log.Infof("Applied user config: %s", PrettyFormatOfMeshConfig(mc)) 184 } 185 } 186 if w.revMeshConfig != "" { 187 mc1, err := ApplyMeshConfig(w.revMeshConfig, mc) 188 if err != nil { 189 log.Errorf("revision config invalid, ignoring it %v %s", err, w.userMeshConfig) 190 } else { 191 mc = mc1 192 log.Infof("Applied revision mesh config: %s", PrettyFormatOfMeshConfig(mc)) 193 } 194 } 195 return mc 196 } 197 198 // HandleMeshConfig calls all handlers for a given mesh configuration update. This must be called 199 // with a lock on w.Mutex, or updates may be applied out of order. 200 func (w *internalWatcher) HandleMeshConfig(meshConfig *meshconfig.MeshConfig) { 201 w.mutex.Lock() 202 defer w.mutex.Unlock() 203 w.handleMeshConfigInternal(meshConfig) 204 } 205 206 // handleMeshConfigInternal behaves the same as HandleMeshConfig but must be called under a lock 207 func (w *internalWatcher) handleMeshConfigInternal(meshConfig *meshconfig.MeshConfig) { 208 var handlers []*WatcherHandlerRegistration 209 210 current := w.MeshConfig.Load() 211 if !reflect.DeepEqual(meshConfig, current) { 212 log.Infof("mesh configuration updated to: %s", PrettyFormatOfMeshConfig(meshConfig)) 213 if !reflect.DeepEqual(meshConfig.ConfigSources, current.ConfigSources) { 214 log.Info("mesh configuration sources have changed") 215 // TODO Need to recreate or reload initConfigController() 216 } 217 218 w.MeshConfig.Store(meshConfig) 219 handlers = append(handlers, w.handlers...) 220 } 221 222 // TODO hack: the first handler added is the ConfigPush, other handlers affect what will be pushed, so reversing iteration 223 for i := len(handlers) - 1; i >= 0; i-- { 224 handlers[i].handler() 225 } 226 } 227 228 // Add to the FileWatcher the provided file and execute the provided function 229 // on any change event for this file. 230 // Using a debouncing mechanism to avoid calling the callback multiple times 231 // per event. 232 func addFileWatcher(fileWatcher filewatcher.FileWatcher, file string, callback func()) { 233 _ = fileWatcher.Add(file) 234 go func() { 235 var timerC <-chan time.Time 236 for { 237 select { 238 case <-timerC: 239 timerC = nil 240 callback() 241 case <-fileWatcher.Events(file): 242 // Use a timer to debounce configuration updates 243 if timerC == nil { 244 timerC = time.After(100 * time.Millisecond) 245 } 246 } 247 } 248 }() 249 } 250 251 func PrettyFormatOfMeshConfig(meshConfig *meshconfig.MeshConfig) string { 252 meshConfigDump, _ := protomarshal.ToJSONWithIndent(meshConfig, " ") 253 return meshConfigDump 254 }