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 }