istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/krt/join.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 krt 16 17 import ( 18 "fmt" 19 20 "istio.io/istio/pkg/ptr" 21 "istio.io/istio/pkg/slices" 22 "istio.io/istio/pkg/util/sets" 23 ) 24 25 type join[T any] struct { 26 collectionName string 27 id collectionUID 28 collections []internalCollection[T] 29 synced <-chan struct{} 30 } 31 32 func (j *join[T]) GetKey(k Key[T]) *T { 33 for _, c := range j.collections { 34 if r := c.GetKey(k); r != nil { 35 return r 36 } 37 } 38 return nil 39 } 40 41 func (j *join[T]) List() []T { 42 res := []T{} 43 found := sets.New[Key[T]]() 44 for _, c := range j.collections { 45 for _, i := range c.List() { 46 key := GetKey(i) 47 if !found.InsertContains(key) { 48 // Only keep it if it is the first time we saw it, as our merging mechanism is to keep the first one 49 res = append(res, i) 50 } 51 } 52 } 53 return res 54 } 55 56 func (j *join[T]) Register(f func(o Event[T])) Syncer { 57 return registerHandlerAsBatched[T](j, f) 58 } 59 60 func (j *join[T]) RegisterBatch(f func(o []Event[T], initialSync bool), runExistingState bool) Syncer { 61 sync := multiSyncer{} 62 for _, c := range j.collections { 63 sync.syncers = append(sync.syncers, c.RegisterBatch(f, runExistingState)) 64 } 65 return sync 66 } 67 68 // nolint: unused // (not true, its to implement an interface) 69 func (j *join[T]) augment(a any) any { 70 // not supported in this collection type 71 return a 72 } 73 74 // nolint: unused // (not true, its to implement an interface) 75 func (j *join[T]) name() string { return j.collectionName } 76 77 // nolint: unused // (not true, its to implement an interface) 78 func (j *join[T]) uid() collectionUID { return j.id } 79 80 // nolint: unused // (not true, its to implement an interface) 81 func (j *join[I]) dump() { 82 log.Errorf("> BEGIN DUMP (join %v)", j.collectionName) 83 for _, c := range j.collections { 84 c.dump() 85 } 86 log.Errorf("< END DUMP (join %v)", j.collectionName) 87 } 88 89 func (j *join[T]) Synced() Syncer { 90 return channelSyncer{ 91 name: j.collectionName, 92 synced: j.synced, 93 } 94 } 95 96 // JoinCollection combines multiple Collection[T] into a single 97 // Collection[T] merging equal objects into one record 98 // in the resulting Collection 99 func JoinCollection[T any](cs []Collection[T], opts ...CollectionOption) Collection[T] { 100 o := buildCollectionOptions(opts...) 101 if o.name == "" { 102 o.name = fmt.Sprintf("Join[%v]", ptr.TypeName[T]()) 103 } 104 synced := make(chan struct{}) 105 c := slices.Map(cs, func(e Collection[T]) internalCollection[T] { 106 return e.(internalCollection[T]) 107 }) 108 go func() { 109 for _, c := range c { 110 if !c.Synced().WaitUntilSynced(o.stop) { 111 return 112 } 113 } 114 close(synced) 115 log.Infof("%v synced", o.name) 116 }() 117 // TODO: in the future, we could have a custom merge function. For now, since we just take the first, we optimize around that case 118 return &join[T]{ 119 collectionName: o.name, 120 id: nextUID(), 121 synced: synced, 122 collections: c, 123 } 124 }