github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/kafka/producer/producer_test.go (about)

     1  // Copyright (c) 2016 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package producer
     6  
     7  import (
     8  	"encoding/json"
     9  	"strings"
    10  	"sync"
    11  	"testing"
    12  
    13  	"github.com/aristanetworks/goarista/kafka/gnmi"
    14  	"github.com/aristanetworks/goarista/test"
    15  
    16  	"github.com/IBM/sarama"
    17  	pb "github.com/openconfig/gnmi/proto/gnmi"
    18  	"google.golang.org/protobuf/proto"
    19  )
    20  
    21  type mockAsyncProducer struct {
    22  	input     chan *sarama.ProducerMessage
    23  	successes chan *sarama.ProducerMessage
    24  	errors    chan *sarama.ProducerError
    25  }
    26  
    27  func newMockAsyncProducer() *mockAsyncProducer {
    28  	return &mockAsyncProducer{
    29  		input:     make(chan *sarama.ProducerMessage),
    30  		successes: make(chan *sarama.ProducerMessage),
    31  		errors:    make(chan *sarama.ProducerError)}
    32  }
    33  
    34  func (p *mockAsyncProducer) AsyncClose() {
    35  	panic("Not implemented")
    36  }
    37  
    38  func (p *mockAsyncProducer) Close() error {
    39  	close(p.successes)
    40  	close(p.errors)
    41  	return nil
    42  }
    43  
    44  func (p *mockAsyncProducer) Input() chan<- *sarama.ProducerMessage {
    45  	return p.input
    46  }
    47  
    48  func (p *mockAsyncProducer) Successes() <-chan *sarama.ProducerMessage {
    49  	return p.successes
    50  }
    51  
    52  func (p *mockAsyncProducer) Errors() <-chan *sarama.ProducerError {
    53  	return p.errors
    54  }
    55  
    56  func (p *mockAsyncProducer) IsTransactional() bool {
    57  	panic("Not implemented")
    58  }
    59  
    60  func (p *mockAsyncProducer) TxnStatus() sarama.ProducerTxnStatusFlag {
    61  	panic("Not implemented")
    62  }
    63  
    64  func (p *mockAsyncProducer) BeginTxn() error {
    65  	panic("Not implemented")
    66  }
    67  
    68  func (p *mockAsyncProducer) CommitTxn() error {
    69  	panic("Not implemented")
    70  }
    71  
    72  func (p *mockAsyncProducer) AbortTxn() error {
    73  	panic("Not implemented")
    74  }
    75  
    76  func (p *mockAsyncProducer) AddOffsetsToTxn(
    77  	offsets map[string][]*sarama.PartitionOffsetMetadata, groupID string) error {
    78  	panic("Not implemented")
    79  }
    80  
    81  func (p *mockAsyncProducer) AddMessageToTxn(
    82  	msg *sarama.ConsumerMessage, groupID string, metadata *string) error {
    83  	panic("Not implemented")
    84  }
    85  
    86  func newPath(path string) *pb.Path {
    87  	if path == "" {
    88  		return nil
    89  	}
    90  	paths := strings.Split(path, "/")
    91  	elems := make([]*pb.PathElem, len(paths))
    92  	for i, elem := range paths {
    93  		elems[i] = &pb.PathElem{Name: elem}
    94  	}
    95  	return &pb.Path{Elem: elems}
    96  }
    97  
    98  func ToStringPtr(str string) *string {
    99  	return &str
   100  }
   101  
   102  func ToFloatPtr(flt float64) *float64 {
   103  	return &flt
   104  }
   105  
   106  func TestKafkaProducer(t *testing.T) {
   107  	mock := newMockAsyncProducer()
   108  	toDB := make(chan proto.Message)
   109  	topic := "occlient"
   110  	systemID := "Foobar"
   111  	toDBProducer := &producer{
   112  		notifsChan:    toDB,
   113  		kafkaProducer: mock,
   114  		encoder:       gnmi.NewEncoder(topic, sarama.StringEncoder(systemID), ""),
   115  		done:          make(chan struct{}),
   116  		wg:            sync.WaitGroup{},
   117  	}
   118  
   119  	toDBProducer.Start()
   120  
   121  	response := &pb.SubscribeResponse{
   122  		Response: &pb.SubscribeResponse_Update{
   123  			Update: &pb.Notification{
   124  				Timestamp: 0,
   125  				Prefix:    newPath("/foo/bar"),
   126  				Update: []*pb.Update{
   127  					&pb.Update{
   128  						Path: newPath("/bar"),
   129  						Val: &pb.TypedValue{
   130  							Value: &pb.TypedValue_IntVal{IntVal: 42},
   131  						}},
   132  				},
   133  			},
   134  		},
   135  	}
   136  	document := map[string]interface{}{
   137  		"Timestamp":   "1",
   138  		"DatasetID":   "foo",
   139  		"Path":        "/foo/bar",
   140  		"Key":         []byte("/bar"),
   141  		"KeyString":   ToStringPtr("/bar"),
   142  		"ValueDouble": ToFloatPtr(float64(42))}
   143  
   144  	toDB <- response
   145  
   146  	kafkaMessage := <-mock.input
   147  	if kafkaMessage.Topic != topic {
   148  		t.Errorf("Unexpected Topic: %s, expecting %s", kafkaMessage.Topic, topic)
   149  	}
   150  	key, err := kafkaMessage.Key.Encode()
   151  	if err != nil {
   152  		t.Fatalf("Error encoding key: %s", err)
   153  	}
   154  	if string(key) != systemID {
   155  		t.Errorf("Kafka message didn't have expected key: %s, expecting %s", string(key), systemID)
   156  	}
   157  
   158  	valueBytes, err := kafkaMessage.Value.Encode()
   159  	if err != nil {
   160  		t.Fatalf("Error encoding value: %s", err)
   161  	}
   162  	var result interface{}
   163  	err = json.Unmarshal(valueBytes, &result)
   164  	if err != nil {
   165  		t.Errorf("Error decoding into JSON: %s", err)
   166  	}
   167  	if !test.DeepEqual(document["update"], result.(map[string]interface{})["update"]) {
   168  		t.Errorf("Protobuf sent from Kafka Producer does not match original.\nOriginal: %v\nNew:%v",
   169  			document, result)
   170  	}
   171  
   172  	toDBProducer.Stop()
   173  }
   174  
   175  type mockMsg struct{}
   176  
   177  func (m mockMsg) Reset()         {}
   178  func (m mockMsg) String() string { return "" }
   179  func (m mockMsg) ProtoMessage()  {}
   180  
   181  func TestProducerStartStop(t *testing.T) {
   182  	// this test checks that Start() followed by Stop() doesn't cause any race conditions.
   183  
   184  	mock := newMockAsyncProducer()
   185  	toDB := make(chan proto.Message)
   186  	topic := "occlient"
   187  	systemID := "Foobar"
   188  	p := &producer{
   189  		notifsChan:    toDB,
   190  		kafkaProducer: mock,
   191  		encoder:       gnmi.NewEncoder(topic, sarama.StringEncoder(systemID), ""),
   192  		done:          make(chan struct{}),
   193  	}
   194  
   195  	msg := &pb.SubscribeResponse{
   196  		Response: &pb.SubscribeResponse_Update{
   197  			Update: &pb.Notification{
   198  				Timestamp: 0,
   199  				Prefix:    newPath("/foo/bar"),
   200  				Update:    []*pb.Update{},
   201  			},
   202  		},
   203  	}
   204  
   205  	done := make(chan struct{})
   206  	var wg sync.WaitGroup
   207  	wg.Add(1)
   208  	go func() {
   209  		defer wg.Done()
   210  		for {
   211  			select {
   212  			case <-mock.input:
   213  			case <-done:
   214  				return
   215  			}
   216  		}
   217  	}()
   218  
   219  	wg.Add(1)
   220  	go func() {
   221  		defer wg.Done()
   222  		for {
   223  			select {
   224  			case <-done:
   225  				return
   226  			default:
   227  			}
   228  			p.Write(msg)
   229  		}
   230  	}()
   231  	p.Start()
   232  	p.Write(msg)
   233  	p.Stop()
   234  	close(done)
   235  	wg.Wait()
   236  }