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  }