github.com/rudderlabs/rudder-go-kit@v0.30.0/kafkaclient/testutil/util.go (about) 1 package testutil 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "strconv" 8 "time" 9 10 "github.com/segmentio/kafka-go" 11 ) 12 13 // NewWithDialer returns a client with a custom dialer 14 func NewWithDialer(dialer *kafka.Dialer, network string, address ...string) *Client { 15 return &Client{ 16 dialer: dialer, 17 network: network, 18 addresses: address, 19 } 20 } 21 22 // New returns a client with a default dialer 23 func New(network, address string) *Client { 24 return NewWithDialer(&kafka.Dialer{ 25 DualStack: true, 26 Timeout: 10 * time.Second, 27 }, network, address) 28 } 29 30 type Client struct { 31 dialer *kafka.Dialer 32 network string 33 addresses []string 34 } 35 36 func (c *Client) ping(ctx context.Context) (*kafka.Conn, error) { 37 var ( 38 err error 39 conn *kafka.Conn 40 ) 41 for _, addr := range c.addresses { 42 conn, err = c.dialer.DialContext(ctx, c.network, kafka.TCP(addr).String()) 43 if err == nil { // we can connect to at least one address, no need to check all of them 44 break 45 } 46 } 47 if err != nil { 48 return nil, fmt.Errorf( 49 "could not dial any of the addresses %s/%s: %w", c.network, kafka.TCP(c.addresses...).String(), err, 50 ) 51 } 52 return conn, nil 53 } 54 55 func (c *Client) CreateTopic(ctx context.Context, topic string, numPartitions, replicationFactor int) error { 56 conn, err := c.ping(ctx) 57 if err != nil { 58 return fmt.Errorf("create topic: cannot ping: %w", err) 59 } 60 61 var ( 62 controllerHost string 63 errors = make(chan error, 1) 64 ) 65 defer close(errors) 66 go func() { // doing it asynchronously because conn.Controller() does not honour the context 67 var b kafka.Broker 68 b, err = conn.Controller() 69 if err != nil { 70 errors <- fmt.Errorf("create topic: could not get controller: %w", err) 71 return 72 } 73 if b.Host == "" { 74 errors <- fmt.Errorf("create topic: controller connection has empty broker host") 75 return 76 } 77 controllerHost = net.JoinHostPort(b.Host, strconv.Itoa(b.Port)) 78 errors <- nil 79 }() 80 81 select { 82 case <-ctx.Done(): 83 return fmt.Errorf("create topic: %w", ctx.Err()) 84 case err := <-errors: 85 if err != nil { 86 return err 87 } 88 } 89 90 controllerConn, err := c.dialer.DialContext(ctx, c.network, controllerHost) 91 if err != nil { 92 return fmt.Errorf("create topic: could not dial controller: %w", err) 93 } 94 defer func() { 95 // close asynchronously, if we block we might not respect the context 96 go func() { _ = controllerConn.Close() }() 97 }() 98 99 go func() { // doing it asynchronously because controllerConn.CreateTopics() does not honour the context 100 errors <- controllerConn.CreateTopics(kafka.TopicConfig{ 101 Topic: topic, 102 NumPartitions: numPartitions, 103 ReplicationFactor: replicationFactor, 104 }) 105 }() 106 107 select { 108 case <-ctx.Done(): 109 return ctx.Err() 110 case err = <-errors: 111 if err != nil { 112 return fmt.Errorf("create topic: cannot create topic with controller %q and addresses %+v: %w", 113 controllerHost, c.addresses, err, 114 ) 115 } 116 return nil 117 } 118 } 119 120 type TopicPartition struct { 121 Topic string 122 Partition int 123 } 124 125 func (c *Client) ListTopics(ctx context.Context) ([]TopicPartition, error) { 126 conn, err := c.ping(ctx) 127 if err != nil { 128 return nil, err 129 } 130 131 var ( 132 done = make(chan []kafka.Partition, 1) 133 errors = make(chan error, 1) 134 ) 135 go func() { // doing it asynchronously because conn.ReadPartitions() does not honour the context 136 partitions, err := conn.ReadPartitions() 137 if err != nil { 138 errors <- err 139 } else { 140 done <- partitions 141 } 142 }() 143 144 select { 145 case <-ctx.Done(): 146 return nil, ctx.Err() 147 case err = <-errors: 148 return nil, fmt.Errorf("could not read partitions: %w", err) 149 case partitions := <-done: 150 var topics []TopicPartition 151 for i := range partitions { 152 topics = append(topics, TopicPartition{ 153 Topic: partitions[i].Topic, 154 Partition: partitions[i].ID, 155 }) 156 } 157 return topics, nil 158 } 159 }