istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/multicluster/cluster.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 multicluster
    16  
    17  import (
    18  	"crypto/sha256"
    19  	"time"
    20  
    21  	"go.uber.org/atomic"
    22  	corev1 "k8s.io/api/core/v1"
    23  
    24  	"istio.io/istio/pilot/pkg/features"
    25  	"istio.io/istio/pkg/cluster"
    26  	"istio.io/istio/pkg/config/mesh"
    27  	"istio.io/istio/pkg/kube"
    28  	"istio.io/istio/pkg/kube/kclient"
    29  	filter "istio.io/istio/pkg/kube/namespace"
    30  	"istio.io/istio/pkg/log"
    31  )
    32  
    33  // Cluster defines cluster struct
    34  type Cluster struct {
    35  	// ID of the cluster.
    36  	ID cluster.ID
    37  	// Client for accessing the cluster.
    38  	Client kube.Client
    39  
    40  	kubeConfigSha [sha256.Size]byte
    41  
    42  	stop chan struct{}
    43  	// initialSync is marked when RunAndWait completes
    44  	initialSync *atomic.Bool
    45  	// initialSyncTimeout is set when RunAndWait timed out
    46  	initialSyncTimeout *atomic.Bool
    47  }
    48  
    49  type ACTION int
    50  
    51  const (
    52  	Add ACTION = iota
    53  	Update
    54  )
    55  
    56  func (a ACTION) String() string {
    57  	switch a {
    58  	case Add:
    59  		return "Add"
    60  	case Update:
    61  		return "Update"
    62  	}
    63  	return "Unknown"
    64  }
    65  
    66  // Run starts the cluster's informers and waits for caches to sync. Once caches are synced, we mark the cluster synced.
    67  // This should be called after each of the handlers have registered informers, and should be run in a goroutine.
    68  func (c *Cluster) Run(mesh mesh.Watcher, handlers []handler, action ACTION) {
    69  	if features.RemoteClusterTimeout > 0 {
    70  		time.AfterFunc(features.RemoteClusterTimeout, func() {
    71  			if !c.initialSync.Load() {
    72  				log.Errorf("remote cluster %s failed to sync after %v", c.ID, features.RemoteClusterTimeout)
    73  				timeouts.With(clusterLabel.Value(string(c.ID))).Increment()
    74  			}
    75  			c.initialSyncTimeout.Store(true)
    76  		})
    77  	}
    78  
    79  	// Build a namespace watcher. This must have no filter, since this is our input to the filter itself.
    80  	// This must be done before we build components, so they can access the filter.
    81  	namespaces := kclient.New[*corev1.Namespace](c.Client)
    82  	// This will start a namespace informer and wait for it to be ready. So we must start it in a go routine to avoid blocking.
    83  	filter := filter.NewDiscoveryNamespacesFilter(namespaces, mesh, c.stop)
    84  	kube.SetObjectFilter(c.Client, filter)
    85  
    86  	syncers := make([]ComponentConstraint, 0, len(handlers))
    87  	for _, h := range handlers {
    88  		switch action {
    89  		case Add:
    90  			syncers = append(syncers, h.clusterAdded(c))
    91  		case Update:
    92  			syncers = append(syncers, h.clusterUpdated(c))
    93  		}
    94  	}
    95  	if !c.Client.RunAndWait(c.stop) {
    96  		log.Warnf("remote cluster %s failed to sync", c.ID)
    97  		return
    98  	}
    99  	for _, h := range syncers {
   100  		if !kube.WaitForCacheSync("cluster"+string(c.ID), c.stop, h.HasSynced) {
   101  			log.Warnf("remote cluster %s failed to sync handler", c.ID)
   102  			return
   103  		}
   104  	}
   105  
   106  	c.initialSync.Store(true)
   107  }
   108  
   109  // Stop closes the stop channel, if is safe to be called multi times.
   110  func (c *Cluster) Stop() {
   111  	select {
   112  	case <-c.stop:
   113  		return
   114  	default:
   115  		close(c.stop)
   116  	}
   117  }
   118  
   119  func (c *Cluster) HasSynced() bool {
   120  	// It could happen when a wrong credential provide, this cluster has no chance to run.
   121  	// In this case, the `initialSyncTimeout` will never be set
   122  	// In order not block istiod start up, check close as well.
   123  	if c.Closed() {
   124  		return true
   125  	}
   126  	return c.initialSync.Load() || c.initialSyncTimeout.Load()
   127  }
   128  
   129  func (c *Cluster) Closed() bool {
   130  	select {
   131  	case <-c.stop:
   132  		return true
   133  	default:
   134  		return false
   135  	}
   136  }
   137  
   138  func (c *Cluster) SyncDidTimeout() bool {
   139  	return !c.initialSync.Load() && c.initialSyncTimeout.Load()
   140  }