@@ -412,7 +412,110 @@ test_partition_count_decrease() ->
412412 ok = application :stop (wolff ),
413413 ok .
414414
415- test_topic_recreate_test_ () ->
415+ % % The last partition is temporarily missing from metadata response.
416+ % % The producer should retry to recover
417+ last_partition_missing_in_metadata_response_test_ () ->
418+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
419+ fun () -> test_partition_missing_in_metadata_response (2 ) end }.
420+
421+ % % The partition in the middle is temporarily missing from metadata response.
422+ % % The producer should retry to recover
423+ mid_partition_missing_in_metadata_response_test_ () ->
424+ {timeout , 30 , % % it takes time to alter topic via cli in docker container
425+ fun () -> test_partition_missing_in_metadata_response (1 ) end }.
426+
427+ test_partition_missing_in_metadata_response (ThePartition ) ->
428+ io :format (user , " test_partition_missing_in_metadata_response\n " , []),
429+ ClientId = <<" test-temporarily-malformed-metadata-response" >>,
430+ % % ensure the topic does not exist
431+ Topic = <<" test-topic-5" >>,
432+ delete_topic (Topic ),
433+ Partitions = 3 ,
434+ create_topic (Topic , Partitions ),
435+ io :format (user , " created topic ~s with ~p partitions\n " , [Topic , Partitions ]),
436+ _ = application :stop (wolff ), % % ensure stopped
437+ {ok , _ } = application :ensure_all_started (wolff ),
438+ % % always refresh metadata
439+ ClientCfg = #{min_metadata_refresh_interval => 0 },
440+ {ok , ClientPid } = wolff :ensure_supervised_client (ClientId , ? HOSTS , ClientCfg ),
441+ {ok , Connections } = get_leader_connections (ClientPid , Topic ),
442+ ? assertEqual (Partitions , length (Connections )),
443+ % % always send it to the partition which is going to be missing
444+ Partitioner = fun (_ , _ ) -> ThePartition end ,
445+ ProducerCfg = #{required_acks => all_isr ,
446+ partitioner => Partitioner ,
447+ reconnect_delay_ms => 0 ,
448+ % % 0 means no aut-refresh
449+ partition_count_refresh_interval_seconds => 0
450+ },
451+ {ok , Producers } = wolff :ensure_supervised_producers (ClientId , Topic , ProducerCfg ),
452+ ? assertEqual (Partitions , wolff_producers :get_partition_cnt (ClientId , ? NO_GROUP , Topic )),
453+ Msg = #{key => ? KEY , value => <<" value" >>},
454+ ? assertMatch ({ThePartition , _ }, wolff :send_sync (Producers , [Msg ], 3000 )),
455+ % % mock a bad
456+ % % Kill partition leader connection
457+ Pid = get_partition_leader_connection (ClientPid , Topic , ThePartition ),
458+ exit (Pid , kill ),
459+ % % mock kafka_protcol to return metadata with ThePartition missing
460+ meck :new (kpro , [passthrough , no_history ]),
461+ meck :expect (kpro , request_sync ,
462+ fun (Connection , Req , Timeout ) ->
463+ {ok , Rsp } = meck :passthrough ([Connection , Req , Timeout ]),
464+ case Rsp of
465+ # kpro_rsp {msg = #{topic_metadata := [#{partition_metadata := PM0 } = TopicMeta ]} = Meta } ->
466+ PM = lists :filter (fun (#{partition := P }) -> P =/= ThePartition end , PM0 ),
467+ NewRsp = Rsp # kpro_rsp {msg = Meta #{topic_metadata := [TopicMeta #{partition_metadata := PM }]}},
468+ {ok , NewRsp };
469+ _ ->
470+ {ok , Rsp }
471+ end
472+ end ),
473+ ? assertNot (is_pid (get_partition_leader_connection (ClientPid , Topic , ThePartition ))),
474+ % % the request will be buffered, but the call times out
475+ Msg2 = #{key => ? KEY , value => <<" v" >>},
476+ ? assertError (timeout , wolff :send_sync (Producers , [Msg2 ], 100 )),
477+ % % Wait for auto recover
478+ Tester = self (),
479+ meck :expect (kpro , send ,
480+ fun (Conn , Req ) ->
481+ Tester ! {sent , Conn },
482+ meck :passthrough ([Conn , Req ])
483+ end ),
484+ % % Now remove the injected error (malformed metadata response)
485+ % % wolff_producer should now be able to recover
486+ meck :expect (kpro , request_sync ,
487+ fun (Connection , Req , Timeout ) ->
488+ meck :passthrough ([Connection , Req , Timeout ])
489+ end ),
490+ receive
491+ {sent , NewConn } ->
492+ ? assertEqual (NewConn , get_partition_leader_connection (ClientPid , Topic , ThePartition ))
493+ after
494+ 2000 ->
495+ % % wolff_producer retry delay is 0, but it randomize with extra 0-1000ms
496+ error (timeout )
497+ end ,
498+ meck :unload (kpro ),
499+ ok = fetch_and_match (ClientPid , Topic , ThePartition , 0 , [Msg , Msg2 ]),
500+ % % cleanup
501+ ok = wolff :stop_and_delete_supervised_producers (Producers ),
502+ ? assertEqual ([], supervisor :which_children (wolff_producers_sup )),
503+ ok = wolff :stop_and_delete_supervised_client (ClientId ),
504+ ? assertEqual ([], supervisor :which_children (wolff_client_sup )),
505+ ok = application :stop (wolff ),
506+ ok .
507+
508+ get_partition_leader_connection (Client , Topic , Partition ) ->
509+ ok = wolff_client :recv_leader_connection (Client , Topic , Partition , self ()),
510+ receive
511+ {leader_connection , Pid } ->
512+ Pid
513+ after
514+ 20000 ->
515+ error (timeout )
516+ end .
517+
518+ topic_recreate_test_ () ->
416519 {timeout , 30 , % % it takes time to alter topic via cli in docker container
417520 fun test_topic_recreate /0 }.
418521
0 commit comments