go.ligato.io/vpp-agent/v3@v3.5.0/plugins/configurator/notify.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 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 configurator 16 17 import ( 18 "log" 19 "sync" 20 "sync/atomic" 21 22 "github.com/google/go-cmp/cmp" 23 "go.ligato.io/cn-infra/v2/logging" 24 "google.golang.org/protobuf/proto" 25 proto3 "google.golang.org/protobuf/proto" 26 "google.golang.org/protobuf/testing/protocmp" 27 28 pb "go.ligato.io/vpp-agent/v3/proto/ligato/configurator" 29 ) 30 31 // Maximum number of messages stored in the buffer. Buffer is always filled from left 32 // to right (it means that if the buffer is full, a new entry is written to the index 0) 33 const bufferSize = 10000 34 35 // notifyService forwards GRPC messages to external servers. 36 type notifyService struct { 37 log logging.Logger 38 39 // VPP notifications available for clients 40 mx sync.RWMutex 41 buffer [bufferSize]*pb.NotifyResponse 42 curIdx uint32 43 watchs chan *watcher 44 notifs chan *pb.NotifyResponse 45 } 46 47 // Notify returns all required VPP notifications (or those available in the buffer) in the same order as they were received 48 func (svc *notifyService) Notify(from *pb.NotifyRequest, server pb.ConfiguratorService_NotifyServer) error { 49 svc.mx.RLock() 50 51 // Copy requested index locally 52 fromIdx := from.Idx 53 54 // Check if requested index overflows buffer length 55 if svc.curIdx-from.Idx > bufferSize { 56 fromIdx = svc.curIdx - bufferSize 57 } 58 59 // Start from requested index until the most recent entry 60 for i := fromIdx; i < svc.curIdx; i++ { 61 entry := svc.buffer[i%bufferSize] 62 if !isFilter(entry.GetNotification(), from.Filters) { 63 continue 64 } 65 if err := server.Send(entry); err != nil { 66 svc.mx.RUnlock() 67 svc.log.Warnf("Notify send error: %v", err) 68 return err 69 } 70 } 71 72 svc.mx.RUnlock() 73 74 w := &watcher{ 75 notifs: make(chan *pb.NotifyResponse), 76 done: make(chan struct{}), 77 filters: from.Filters, 78 } 79 80 select { 81 case svc.watchs <- w: 82 case <-server.Context().Done(): 83 return server.Context().Err() 84 } 85 86 defer func() { 87 close(w.done) 88 }() 89 90 for { 91 select { 92 case n := <-w.notifs: 93 if err := server.Send(n); err != nil { 94 svc.log.Warnf("Notify send error: %v", err) 95 return err 96 } 97 case <-server.Context().Done(): 98 return server.Context().Err() 99 } 100 } 101 } 102 103 type watcher struct { 104 notifs chan *pb.NotifyResponse 105 done chan struct{} 106 filters []*pb.Notification 107 } 108 109 // Pushes new notification to the buffer. The order of notifications is preserved. 110 func (svc *notifyService) pushNotification(notification *pb.Notification) { 111 // notification is cloned to ensure it does not get changed after storing 112 notifCopy := proto.Clone(notification).(*pb.Notification) 113 114 // Notification index starts with 1 115 idx := atomic.LoadUint32(&svc.curIdx) 116 notif := &pb.NotifyResponse{ 117 NextIdx: atomic.AddUint32(&svc.curIdx, 1), 118 Notification: notifCopy, 119 } 120 svc.notifs <- notif 121 122 svc.mx.Lock() 123 defer svc.mx.Unlock() 124 125 svc.buffer[idx%bufferSize] = notif 126 } 127 128 func (svc *notifyService) init() { 129 svc.notifs = make(chan *pb.NotifyResponse) 130 svc.watchs = make(chan *watcher) 131 132 var watchers []*watcher 133 134 go func() { 135 for { 136 select { 137 case n := <-svc.notifs: 138 for i, w := range watchers { 139 select { 140 case <-w.done: 141 svc.log.Infof("removing watcher") 142 copy(watchers[i:], watchers[i+1:]) // Shift a[i+1:] left one index. 143 watchers[len(watchers)-1] = nil // Erase last element (write zero value). 144 watchers = watchers[:len(watchers)-1] // Truncate slice. 145 continue 146 default: 147 } 148 if !isFilter(n.GetNotification(), w.filters) { 149 continue 150 } 151 select { 152 case w.notifs <- n: 153 default: 154 svc.log.Infof("watcher full") 155 } 156 } 157 158 case w := <-svc.watchs: 159 watchers = append(watchers, w) 160 } 161 } 162 }() 163 } 164 165 func isFilter(n *pb.Notification, filters []*pb.Notification) bool { 166 if len(filters) == 0 { 167 return true 168 } 169 170 for _, f := range filters { 171 transforms := []cmp.Option{ 172 protocmp.IgnoreDefaultScalars(), 173 protocmp.IgnoreEmptyMessages(), 174 protocmp.Transform(), 175 } 176 dst := proto3.Clone(n) 177 proto3.Merge(dst, f) 178 if diff := cmp.Diff(dst, n, transforms...); diff != "" { 179 log.Printf("diff mismatch (-want +got):\n%s", diff) 180 } else { 181 return true 182 } 183 } 184 185 return false 186 }