Skip to content

Commit d33e005

Browse files
committed
Implement Client.WritablePartitions
It returns the list of partition IDs (like normal Client.Partitions) except limited to those partitions which have leaders that are available and accepting writes. Bonus fixes: - permit setting the error field in MetadataResponse.AddTopicPartition - add "closed" check to Replicas and ReplicasInSync - permit MetadataRetries == 0, which is at least logically coherent (and behaves sanely), even if it's not usually useful
1 parent 1b465e7 commit d33e005

5 files changed

Lines changed: 76 additions & 29 deletions

File tree

client.go

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,39 @@ func (client *Client) Close() error {
115115
return nil
116116
}
117117

118-
// Partitions returns the sorted list of available partition IDs for the given topic.
118+
// Partitions returns the sorted list of all partition IDs for the given topic.
119119
func (client *Client) Partitions(topic string) ([]int32, error) {
120120
// Check to see whether the client is closed
121121
if client.Closed() {
122122
return nil, ClosedClient
123123
}
124124

125-
partitions := client.cachedPartitions(topic)
125+
partitions := client.cachedPartitions(topic, false)
126+
127+
if len(partitions) == 0 {
128+
err := client.RefreshTopicMetadata(topic)
129+
if err != nil {
130+
return nil, err
131+
}
132+
partitions = client.cachedPartitions(topic, false)
133+
}
134+
135+
if partitions == nil {
136+
return nil, UnknownTopicOrPartition
137+
}
138+
139+
return partitions, nil
140+
}
141+
142+
// WritablePartitions returns the sorted list of all writable partition IDs for the given topic,
143+
// where "writable" means "having a valid leader accepting writes".
144+
func (client *Client) WritablePartitions(topic string) ([]int32, error) {
145+
// Check to see whether the client is closed
146+
if client.Closed() {
147+
return nil, ClosedClient
148+
}
149+
150+
partitions := client.cachedPartitions(topic, true)
126151

127152
// len==0 catches when it's nil (no such topic) and the odd case when every single
128153
// partition is undergoing leader election simultaneously. Callers have to be able to handle
@@ -135,7 +160,7 @@ func (client *Client) Partitions(topic string) ([]int32, error) {
135160
if err != nil {
136161
return nil, err
137162
}
138-
partitions = client.cachedPartitions(topic)
163+
partitions = client.cachedPartitions(topic, true)
139164
}
140165

141166
if partitions == nil {
@@ -182,6 +207,10 @@ func (client *Client) getMetadata(topic string, partitionID int32) (*PartitionMe
182207
}
183208

184209
func (client *Client) Replicas(topic string, partitionID int32) ([]int32, error) {
210+
if client.Closed() {
211+
return nil, ClosedClient
212+
}
213+
185214
metadata, err := client.getMetadata(topic, partitionID)
186215

187216
if err != nil {
@@ -195,6 +224,10 @@ func (client *Client) Replicas(topic string, partitionID int32) ([]int32, error)
195224
}
196225

197226
func (client *Client) ReplicasInSync(topic string, partitionID int32) ([]int32, error) {
227+
if client.Closed() {
228+
return nil, ClosedClient
229+
}
230+
198231
metadata, err := client.getMetadata(topic, partitionID)
199232

200233
if err != nil {
@@ -418,7 +451,7 @@ func (client *Client) cachedMetadata(topic string, partitionID int32) *Partition
418451
return nil
419452
}
420453

421-
func (client *Client) cachedPartitions(topic string) []int32 {
454+
func (client *Client) cachedPartitions(topic string, onlyWritable bool) []int32 {
422455
client.lock.RLock()
423456
defer client.lock.RUnlock()
424457

@@ -428,8 +461,11 @@ func (client *Client) cachedPartitions(topic string) []int32 {
428461
}
429462

430463
ret := make([]int32, 0, len(partitions))
431-
for id := range partitions {
432-
ret = append(ret, id)
464+
for _, partition := range partitions {
465+
if onlyWritable && partition.Err == LeaderNotAvailable {
466+
continue
467+
}
468+
ret = append(ret, partition.ID)
433469
}
434470

435471
sort.Sort(int32Slice(ret))
@@ -529,12 +565,12 @@ func NewClientConfig() *ClientConfig {
529565
// Validate checks a ClientConfig instance. This will return a
530566
// ConfigurationError if the specified values don't make sense.
531567
func (config *ClientConfig) Validate() error {
532-
if config.MetadataRetries <= 0 {
533-
return ConfigurationError("Invalid MetadataRetries. Try 10")
568+
if config.MetadataRetries < 0 {
569+
return ConfigurationError("Invalid MetadataRetries")
534570
}
535571

536572
if config.WaitForElection <= time.Duration(0) {
537-
return ConfigurationError("Invalid WaitForElection. Try 250*time.Millisecond")
573+
return ConfigurationError("Invalid WaitForElection")
538574
}
539575

540576
if config.DefaultBrokerConf != nil {

client_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,13 @@ func TestClientMetadata(t *testing.T) {
6060

6161
mdr := new(MetadataResponse)
6262
mdr.AddBroker(mb5.Addr(), mb5.BrokerID())
63-
mdr.AddTopicPartition("my_topic", 0, mb5.BrokerID(), replicas, isr)
63+
mdr.AddTopicPartition("my_topic", 0, mb5.BrokerID(), replicas, isr, NoError)
64+
mdr.AddTopicPartition("my_topic", 1, mb5.BrokerID(), replicas, isr, LeaderNotAvailable)
6465
mb1.Returns(mdr)
6566

66-
client, err := NewClient("client_id", []string{mb1.Addr()}, nil)
67+
config := NewClientConfig()
68+
config.MetadataRetries = 0
69+
client, err := NewClient("client_id", []string{mb1.Addr()}, config)
6770
if err != nil {
6871
t.Fatal(err)
6972
}
@@ -81,10 +84,17 @@ func TestClientMetadata(t *testing.T) {
8184
parts, err := client.Partitions("my_topic")
8285
if err != nil {
8386
t.Error(err)
84-
} else if len(parts) != 1 || parts[0] != 0 {
87+
} else if len(parts) != 2 || parts[0] != 0 || parts[1] != 1 {
8588
t.Error("Client returned incorrect partitions for my_topic:", parts)
8689
}
8790

91+
parts, err = client.WritablePartitions("my_topic")
92+
if err != nil {
93+
t.Error(err)
94+
} else if len(parts) != 1 || parts[0] != 0 {
95+
t.Error("Client returned incorrect writable partitions for my_topic:", parts)
96+
}
97+
8898
tst, err := client.Leader("my_topic", 0)
8999
if err != nil {
90100
t.Error(err)
@@ -122,16 +132,13 @@ func TestClientRefreshBehaviour(t *testing.T) {
122132
mb1.Returns(mdr)
123133

124134
mdr2 := new(MetadataResponse)
125-
mdr2.AddTopicPartition("my_topic", 0xb, mb5.BrokerID(), nil, nil)
135+
mdr2.AddTopicPartition("my_topic", 0xb, mb5.BrokerID(), nil, nil, NoError)
126136
mb5.Returns(mdr2)
127137

128138
client, err := NewClient("clientID", []string{mb1.Addr()}, nil)
129139
if err != nil {
130140
t.Fatal(err)
131141
}
132-
defer safeClose(t, client)
133-
defer mb1.Close()
134-
defer mb5.Close()
135142

136143
parts, err := client.Partitions("my_topic")
137144
if err != nil {
@@ -148,4 +155,7 @@ func TestClientRefreshBehaviour(t *testing.T) {
148155
}
149156

150157
client.disconnectBroker(tst)
158+
mb5.Close()
159+
mb1.Close()
160+
safeClose(t, client)
151161
}

consumer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestSimpleConsumer(t *testing.T) {
1919

2020
mdr := new(MetadataResponse)
2121
mdr.AddBroker(mb2.Addr(), mb2.BrokerID())
22-
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil)
22+
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
2323
mb1.Returns(mdr)
2424

2525
for i := 0; i < 10; i++ {
@@ -62,7 +62,7 @@ func TestConsumerRawOffset(t *testing.T) {
6262

6363
mdr := new(MetadataResponse)
6464
mdr.AddBroker(mb2.Addr(), mb2.BrokerID())
65-
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil)
65+
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
6666
mb1.Returns(mdr)
6767

6868
client, err := NewClient("client_id", []string{mb1.Addr()}, nil)
@@ -95,7 +95,7 @@ func TestConsumerLatestOffset(t *testing.T) {
9595

9696
mdr := new(MetadataResponse)
9797
mdr.AddBroker(mb2.Addr(), mb2.BrokerID())
98-
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil)
98+
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
9999
mb1.Returns(mdr)
100100

101101
or := new(OffsetResponse)
@@ -130,7 +130,7 @@ func TestConsumerPrelude(t *testing.T) {
130130

131131
mdr := new(MetadataResponse)
132132
mdr.AddBroker(mb2.Addr(), mb2.BrokerID())
133-
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil)
133+
mdr.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
134134
mb1.Returns(mdr)
135135

136136
fr := new(FetchResponse)

metadata_response.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ func (m *MetadataResponse) AddBroker(addr string, id int32) {
182182
m.Brokers = append(m.Brokers, &Broker{id: id, addr: addr})
183183
}
184184

185-
func (m *MetadataResponse) AddTopicPartition(topic string, partition, brokerID int32, replicas, isr []int32) {
185+
func (m *MetadataResponse) AddTopicPartition(topic string, partition, brokerID int32, replicas, isr []int32, err KError) {
186186
var match *TopicMetadata
187187

188188
for _, tm := range m.Topics {
@@ -216,5 +216,6 @@ foundPartition:
216216
pmatch.Leader = brokerID
217217
pmatch.Replicas = replicas
218218
pmatch.Isr = isr
219+
pmatch.Err = err
219220

220221
}

producer_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestSimpleProducer(t *testing.T) {
2121

2222
response1 := new(MetadataResponse)
2323
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
24-
response1.AddTopicPartition("my_topic", 0, 2, nil, nil)
24+
response1.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
2525
broker1.Returns(response1)
2626

2727
response2 := new(ProduceResponse)
@@ -59,7 +59,7 @@ func TestConcurrentSimpleProducer(t *testing.T) {
5959

6060
response1 := new(MetadataResponse)
6161
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
62-
response1.AddTopicPartition("my_topic", 0, 2, nil, nil)
62+
response1.AddTopicPartition("my_topic", 0, 2, nil, nil, NoError)
6363
broker1.Returns(response1)
6464

6565
response2 := new(ProduceResponse)
@@ -105,7 +105,7 @@ func TestProducer(t *testing.T) {
105105

106106
response1 := new(MetadataResponse)
107107
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
108-
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil)
108+
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil, NoError)
109109
broker1.Returns(response1)
110110

111111
response2 := new(ProduceResponse)
@@ -153,7 +153,7 @@ func TestProducerMultipleFlushes(t *testing.T) {
153153

154154
response1 := new(MetadataResponse)
155155
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
156-
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil)
156+
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil, NoError)
157157
broker1.Returns(response1)
158158

159159
response2 := new(ProduceResponse)
@@ -208,8 +208,8 @@ func TestProducerMultipleBrokers(t *testing.T) {
208208
response1 := new(MetadataResponse)
209209
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
210210
response1.AddBroker(broker3.Addr(), broker3.BrokerID())
211-
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil)
212-
response1.AddTopicPartition("my_topic", 1, broker3.BrokerID(), nil, nil)
211+
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil, NoError)
212+
response1.AddTopicPartition("my_topic", 1, broker3.BrokerID(), nil, nil, NoError)
213213
broker1.Returns(response1)
214214

215215
response2 := new(ProduceResponse)
@@ -261,7 +261,7 @@ func TestProducerFailureRetry(t *testing.T) {
261261

262262
response1 := new(MetadataResponse)
263263
response1.AddBroker(broker2.Addr(), broker2.BrokerID())
264-
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil)
264+
response1.AddTopicPartition("my_topic", 0, broker2.BrokerID(), nil, nil, NoError)
265265
broker1.Returns(response1)
266266

267267
client, err := NewClient("client_id", []string{broker1.Addr()}, nil)
@@ -287,7 +287,7 @@ func TestProducerFailureRetry(t *testing.T) {
287287

288288
response3 := new(MetadataResponse)
289289
response3.AddBroker(broker3.Addr(), broker3.BrokerID())
290-
response3.AddTopicPartition("my_topic", 0, broker3.BrokerID(), nil, nil)
290+
response3.AddTopicPartition("my_topic", 0, broker3.BrokerID(), nil, nil, NoError)
291291
broker2.Returns(response3)
292292

293293
response4 := new(ProduceResponse)

0 commit comments

Comments
 (0)