istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/util/xdsfake/updater.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 xdsfake
    16  
    17  import (
    18  	"sort"
    19  	"strings"
    20  	"time"
    21  
    22  	"istio.io/istio/pilot/pkg/model"
    23  	"istio.io/istio/pkg/cluster"
    24  	"istio.io/istio/pkg/log"
    25  	"istio.io/istio/pkg/slices"
    26  	"istio.io/istio/pkg/test"
    27  )
    28  
    29  // NewFakeXDS creates a XdsUpdater reporting events via a channel.
    30  func NewFakeXDS() *Updater {
    31  	return &Updater{
    32  		SplitEvents: false,
    33  		Events:      make(chan Event, 100),
    34  	}
    35  }
    36  
    37  // NewWithDelegate creates a XdsUpdater reporting events via a channel.
    38  func NewWithDelegate(delegate model.XDSUpdater) *Updater {
    39  	return &Updater{
    40  		Events:   make(chan Event, 100),
    41  		Delegate: delegate,
    42  	}
    43  }
    44  
    45  // Updater is used to test the registry.
    46  type Updater struct {
    47  	// Events tracks notifications received by the updater
    48  	Events   chan Event
    49  	Delegate model.XDSUpdater
    50  	// If SplitEvents is true, updates changing multiple objects will be split into multiple events with 1 item each
    51  	// otherwise they are joined as a CSV
    52  	SplitEvents bool
    53  }
    54  
    55  var _ model.XDSUpdater = &Updater{}
    56  
    57  func (fx *Updater) ConfigUpdate(req *model.PushRequest) {
    58  	names := []string{}
    59  	if req != nil && len(req.ConfigsUpdated) > 0 {
    60  		for key := range req.ConfigsUpdated {
    61  			names = append(names, key.Name)
    62  		}
    63  	}
    64  	sort.Strings(names)
    65  	if fx.SplitEvents {
    66  		for _, n := range names {
    67  			event := "xds"
    68  			if req.Full {
    69  				event += " full"
    70  			}
    71  			select {
    72  			case fx.Events <- Event{Type: event, ID: n}:
    73  			default:
    74  			}
    75  		}
    76  	} else {
    77  		id := strings.Join(names, ",")
    78  		event := "xds"
    79  		if req.Full {
    80  			event += " full"
    81  		}
    82  		select {
    83  		case fx.Events <- Event{Type: event, ID: id, Reason: req.Reason}:
    84  		default:
    85  		}
    86  	}
    87  	if fx.Delegate != nil {
    88  		fx.Delegate.ConfigUpdate(req)
    89  	}
    90  }
    91  
    92  func (fx *Updater) ProxyUpdate(c cluster.ID, ip string) {
    93  	select {
    94  	case fx.Events <- Event{Type: "proxy", ID: ip}:
    95  	default:
    96  	}
    97  	if fx.Delegate != nil {
    98  		fx.Delegate.ProxyUpdate(c, ip)
    99  	}
   100  }
   101  
   102  // Event is used to watch XdsEvents
   103  type Event struct {
   104  	// Type of the event
   105  	Type string
   106  
   107  	// The id of the event
   108  	ID string
   109  
   110  	Reason model.ReasonStats
   111  
   112  	Namespace string
   113  
   114  	// The endpoints associated with an EDS push if any
   115  	Endpoints []*model.IstioEndpoint
   116  
   117  	// EndpointCount, used in matches only
   118  	EndpointCount int
   119  }
   120  
   121  func (fx *Updater) EDSUpdate(c model.ShardKey, hostname string, ns string, entry []*model.IstioEndpoint) {
   122  	select {
   123  	case fx.Events <- Event{Type: "eds", ID: hostname, Endpoints: entry, Namespace: ns}:
   124  	default:
   125  	}
   126  	if fx.Delegate != nil {
   127  		fx.Delegate.EDSUpdate(c, hostname, ns, entry)
   128  	}
   129  }
   130  
   131  func (fx *Updater) EDSCacheUpdate(c model.ShardKey, hostname, ns string, entry []*model.IstioEndpoint) {
   132  	select {
   133  	case fx.Events <- Event{Type: "eds cache", ID: hostname, Endpoints: entry, Namespace: ns}:
   134  	default:
   135  	}
   136  	if fx.Delegate != nil {
   137  		fx.Delegate.EDSCacheUpdate(c, hostname, ns, entry)
   138  	}
   139  }
   140  
   141  // SvcUpdate is called when a service port mapping definition is updated.
   142  // This interface is WIP - labels, annotations and other changes to service may be
   143  // updated to force a EDS and CDS recomputation and incremental push, as it doesn't affect
   144  // LDS/RDS.
   145  func (fx *Updater) SvcUpdate(c model.ShardKey, hostname string, ns string, ev model.Event) {
   146  	select {
   147  	case fx.Events <- Event{Type: "service", ID: hostname, Namespace: ns}:
   148  	default:
   149  	}
   150  	if fx.Delegate != nil {
   151  		fx.Delegate.SvcUpdate(c, hostname, ns, ev)
   152  	}
   153  }
   154  
   155  func (fx *Updater) RemoveShard(shardKey model.ShardKey) {
   156  	select {
   157  	case fx.Events <- Event{Type: "removeShard", ID: shardKey.String()}:
   158  	default:
   159  	}
   160  	if fx.Delegate != nil {
   161  		fx.Delegate.RemoveShard(shardKey)
   162  	}
   163  }
   164  
   165  func (fx *Updater) WaitOrFail(t test.Failer, et string) *Event {
   166  	t.Helper()
   167  	delay := time.NewTimer(time.Second * 5)
   168  	defer delay.Stop()
   169  	for {
   170  		select {
   171  		case e := <-fx.Events:
   172  			if e.Type == et {
   173  				return &e
   174  			}
   175  			log.Infof("skipping event %q want %q", e.Type, et)
   176  			continue
   177  		case <-delay.C:
   178  			t.Fatalf("timed out waiting for %v", et)
   179  		}
   180  	}
   181  }
   182  
   183  // MatchOrFail expects the provided events to arrive, skipping unmatched events
   184  func (fx *Updater) MatchOrFail(t test.Failer, events ...Event) {
   185  	t.Helper()
   186  	fx.matchOrFail(t, false, events...)
   187  }
   188  
   189  // StrictMatchOrFail expects the provided events to arrive, and nothing else
   190  func (fx *Updater) StrictMatchOrFail(t test.Failer, events ...Event) {
   191  	t.Helper()
   192  	fx.matchOrFail(t, true, events...)
   193  }
   194  
   195  func (fx *Updater) matchOrFail(t test.Failer, strict bool, events ...Event) {
   196  	t.Helper()
   197  	delay := time.NewTimer(time.Second * 5)
   198  	defer delay.Stop()
   199  	for {
   200  		if len(events) == 0 {
   201  			return
   202  		}
   203  		select {
   204  		case e := <-fx.Events:
   205  			found := false
   206  			for i, want := range events {
   207  				if e.Type == want.Type &&
   208  					(want.ID == "" || e.ID == want.ID) &&
   209  					(want.Namespace == "" || want.Namespace == e.Namespace) &&
   210  					(want.EndpointCount == 0 || want.EndpointCount == len(e.Endpoints)) {
   211  					// Matched - delete event from desired
   212  					events = slices.Delete(events, i)
   213  					found = true
   214  					break
   215  				}
   216  			}
   217  			if !found {
   218  				if strict {
   219  					t.Fatalf("unexpected event %q/%v", e.Type, e.ID)
   220  				} else {
   221  					log.Infof("skipping event %q/%v", e.Type, e.ID)
   222  				}
   223  			}
   224  			continue
   225  		case <-delay.C:
   226  			t.Fatalf("timed out waiting for %v", events)
   227  		}
   228  	}
   229  }
   230  
   231  // Clear any pending event
   232  func (fx *Updater) Clear() {
   233  	wait := true
   234  	for wait {
   235  		select {
   236  		case e := <-fx.Events:
   237  			log.Infof("skipping event (due to clear) %q", e.Type)
   238  		default:
   239  			wait = false
   240  		}
   241  	}
   242  }
   243  
   244  // AssertEmpty ensures there are no events in the channel
   245  func (fx *Updater) AssertEmpty(t test.Failer, dur time.Duration) {
   246  	t.Helper()
   247  	if dur == 0 {
   248  		select {
   249  		case e := <-fx.Events:
   250  			t.Fatalf("got unexpected event %+v", e)
   251  		default:
   252  		}
   253  	} else {
   254  		select {
   255  		case e := <-fx.Events:
   256  			t.Fatalf("got unexpected event %+v", e)
   257  		case <-time.After(dur):
   258  		}
   259  	}
   260  }