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 }