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  }