@@ -210,7 +210,7 @@ partition_count_decrease_test_() ->
210210
211211test_partition_count_decrease () ->
212212 io :format (user , " test_partition_count_decrease\n " , []),
213- ClientId = <<" test-add-more -partitions" >>,
213+ ClientId = <<" test-recreate-topic-with-fewer -partitions" >>,
214214 % % ensure the topic does not exist
215215 Topic = <<" test-topic-4" >>,
216216 delete_topic (Topic ),
@@ -273,7 +273,112 @@ test_partition_count_decrease() ->
273273 ok = application :stop (wolff ),
274274 ok .
275275
276- test_topic_recreate_test_ () ->
276+ % % The last partition is temporarily missing from metadata response.
277+ % % The producer should retry to recover
278+ last_partition_missing_in_metadata_response_test_ () ->
279+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
280+ fun () -> test_partition_missing_in_metadata_response (2 ) end }.
281+
282+ % % The partition in the middle is temporarily missing from metadata response.
283+ % % The producer should retry to recover
284+ mid_partition_missing_in_metadata_response_test_ () ->
285+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
286+ fun () -> test_partition_missing_in_metadata_response (1 ) end }.
287+
288+ test_partition_missing_in_metadata_response (ThePartition ) ->
289+ io :format (user , " test_partition_missing_in_metadata_response\n " , []),
290+ ClientId = <<" test-temporarily-malformed-metadata-response" >>,
291+ % % ensure the topic does not exist
292+ Topic = <<" test-topic-5" >>,
293+ delete_topic (Topic ),
294+ Partitions = 3 ,
295+ create_topic (Topic , Partitions ),
296+ io :format (user , " created topic ~s with ~p partitions\n " , [Topic , Partitions ]),
297+ _ = application :stop (wolff ), % % ensure stopped
298+ {ok , _ } = application :ensure_all_started (wolff ),
299+ % % always refresh metadata
300+ ClientCfg = #{min_metadata_refresh_interval => 0 },
301+ {ok , ClientPid } = wolff :ensure_supervised_client (ClientId , ? HOSTS , ClientCfg ),
302+ {ok , Connections } = wolff_client :get_leader_connections (ClientPid , Topic ),
303+ ? assertEqual (Partitions , length (Connections )),
304+ % % always send it to the partition which is going to be missing
305+ Partitioner = fun (_ , _ ) -> ThePartition end ,
306+ Name = ? FUNCTION_NAME ,
307+ ProducerCfg = #{required_acks => all_isr ,
308+ partitioner => Partitioner ,
309+ reconnect_delay_ms => 0 ,
310+ name => Name ,
311+ % % 0 means no aut-refresh
312+ partition_count_refresh_interval_seconds => 0
313+ },
314+ {ok , Producers } = wolff :ensure_supervised_producers (ClientId , Topic , ProducerCfg ),
315+ ? assertEqual (Partitions , length (ets :tab2list (Name ))),
316+ Msg = #{key => ? KEY , value => <<" value" >>},
317+ ? assertMatch ({ThePartition , _ }, wolff :send_sync (Producers , [Msg ], 3000 )),
318+ % % mock a bad
319+ % % Kill partition leader connection
320+ Pid = get_partition_leader_connection (ClientPid , Topic , ThePartition ),
321+ exit (Pid , kill ),
322+ % % mock kafka_protcol to return metadata with ThePartition missing
323+ meck :new (kpro , [passthrough , no_history ]),
324+ meck :expect (kpro , request_sync ,
325+ fun (Connection , Req , Timeout ) ->
326+ {ok , Rsp } = meck :passthrough ([Connection , Req , Timeout ]),
327+ case Rsp of
328+ # kpro_rsp {msg = #{topic_metadata := [#{partition_metadata := PM0 } = TopicMeta ]} = Meta } ->
329+ PM = lists :filter (fun (#{partition := P }) -> P =/= ThePartition end , PM0 ),
330+ NewRsp = Rsp # kpro_rsp {msg = Meta #{topic_metadata := [TopicMeta #{partition_metadata := PM }]}},
331+ {ok , NewRsp };
332+ _ ->
333+ {ok , Rsp }
334+ end
335+ end ),
336+ ? assertNot (is_pid (get_partition_leader_connection (ClientPid , Topic , ThePartition ))),
337+ % % the request will be buffered, but the call times out
338+ Msg2 = #{key => ? KEY , value => <<" v" >>},
339+ ? assertError (timeout , wolff :send_sync (Producers , [Msg2 ], 100 )),
340+ % % Wait for auto recover
341+ Tester = self (),
342+ meck :expect (kpro , send ,
343+ fun (Conn , Req ) ->
344+ Tester ! {sent , Conn },
345+ meck :passthrough ([Conn , Req ])
346+ end ),
347+ % % Now remove the injected error (malformed metadata response)
348+ % % wolff_producer should now be able to recover
349+ meck :expect (kpro , request_sync ,
350+ fun (Connection , Req , Timeout ) ->
351+ meck :passthrough ([Connection , Req , Timeout ])
352+ end ),
353+ receive
354+ {sent , NewConn } ->
355+ ? assertEqual (NewConn , get_partition_leader_connection (ClientPid , Topic , ThePartition ))
356+ after
357+ 2000 ->
358+ % % wolff_producer retry delay is 0, but it randomize with extra 0-1000ms
359+ error (timeout )
360+ end ,
361+ meck :unload (kpro ),
362+ ok = fetch_and_match (ClientPid , Topic , ThePartition , 0 , [Msg , Msg2 ]),
363+ % % cleanup
364+ ok = wolff :stop_and_delete_supervised_producers (ClientId , Topic , Name ),
365+ ? assertEqual ([], supervisor :which_children (wolff_producers_sup )),
366+ ok = wolff :stop_and_delete_supervised_client (ClientId ),
367+ ? assertEqual ([], supervisor :which_children (wolff_client_sup )),
368+ ok = application :stop (wolff ),
369+ ok .
370+
371+ get_partition_leader_connection (Client , Topic , Partition ) ->
372+ ok = wolff_client :recv_leader_connection (Client , Topic , Partition , self ()),
373+ receive
374+ {leader_connection , Pid } ->
375+ Pid
376+ after
377+ 20000 ->
378+ error (timeout )
379+ end .
380+
381+ topic_recreate_test_ () ->
277382 {timeout , 30 , % % it takes time to alter topic via cli in docker container
278383 fun test_topic_recreate /0 }.
279384
0 commit comments