@platformatic/rdkafka 4.0.1 → 4.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/README.md +5 -5
  2. package/bench/seed.sh +0 -0
  3. package/binding.gyp +8 -1
  4. package/ci/prepublish.js +4 -4
  5. package/config.d.ts +126 -10
  6. package/configure +0 -0
  7. package/deps/librdkafka/.semaphore/run-all-tests.yml +77 -0
  8. package/deps/librdkafka/.semaphore/semaphore-integration.yml +250 -0
  9. package/deps/librdkafka/.semaphore/semaphore.yml +67 -44
  10. package/deps/librdkafka/.semaphore/verify-linux-packages.yml +41 -0
  11. package/deps/librdkafka/CHANGELOG.md +471 -1
  12. package/deps/librdkafka/CONFIGURATION.md +29 -9
  13. package/deps/librdkafka/INTRODUCTION.md +909 -601
  14. package/deps/librdkafka/LICENSES.txt +592 -1
  15. package/deps/librdkafka/Makefile +2 -1
  16. package/deps/librdkafka/configure +0 -0
  17. package/deps/librdkafka/debian/rules +0 -0
  18. package/deps/librdkafka/dev-conf.sh +0 -0
  19. package/deps/librdkafka/examples/consumer.c +18 -8
  20. package/deps/librdkafka/examples/describe_consumer_groups.c +25 -6
  21. package/deps/librdkafka/examples/incremental_alter_configs.c +4 -5
  22. package/deps/librdkafka/examples/kafkatest_verifiable_client.cpp +26 -42
  23. package/deps/librdkafka/examples/openssl_engine_example.cpp +3 -4
  24. package/deps/librdkafka/examples/producer.cpp +2 -2
  25. package/deps/librdkafka/examples/user_scram.c +4 -5
  26. package/deps/librdkafka/examples/win_ssl_cert_store.cpp +4 -3
  27. package/deps/librdkafka/lds-gen.py +0 -0
  28. package/deps/librdkafka/mklove/Makefile.base +0 -0
  29. package/deps/librdkafka/packaging/RELEASE.md +8 -0
  30. package/deps/librdkafka/packaging/alpine/build-alpine.sh +0 -0
  31. package/deps/librdkafka/packaging/cp/README.md +4 -1
  32. package/deps/librdkafka/packaging/cp/check_features.c +12 -4
  33. package/deps/librdkafka/packaging/cp/verify-deb.sh +6 -6
  34. package/deps/librdkafka/packaging/cp/verify-packages.sh +52 -31
  35. package/deps/librdkafka/packaging/cp/verify-rpm.sh +6 -13
  36. package/deps/librdkafka/packaging/debian/rules +0 -0
  37. package/deps/librdkafka/packaging/get_version.py +0 -0
  38. package/deps/librdkafka/packaging/homebrew/brew-update-pr.sh +0 -0
  39. package/deps/librdkafka/packaging/mingw-w64/{travis-before-install.sh → export-variables.sh} +0 -7
  40. package/deps/librdkafka/packaging/mingw-w64/semaphoreci-build.sh +1 -1
  41. package/deps/librdkafka/packaging/nuget/artifact.py +0 -0
  42. package/deps/librdkafka/packaging/nuget/cleanup-s3.py +0 -0
  43. package/deps/librdkafka/packaging/nuget/nuget.sh +0 -0
  44. package/deps/librdkafka/packaging/nuget/packaging.py +0 -0
  45. package/deps/librdkafka/packaging/nuget/push-to-nuget.sh +0 -0
  46. package/deps/librdkafka/packaging/nuget/release.py +0 -0
  47. package/deps/librdkafka/packaging/nuget/requirements.txt +3 -3
  48. package/deps/librdkafka/packaging/rpm/mock-on-docker.sh +0 -0
  49. package/deps/librdkafka/packaging/rpm/tests/run-test.sh +0 -0
  50. package/deps/librdkafka/packaging/rpm/tests/test-on-docker.sh +0 -0
  51. package/deps/librdkafka/packaging/tools/build-configurations-checks.sh +3 -1
  52. package/deps/librdkafka/packaging/tools/build-deb-package.sh +0 -0
  53. package/deps/librdkafka/packaging/tools/build-debian.sh +0 -0
  54. package/deps/librdkafka/packaging/tools/build-manylinux.sh +0 -0
  55. package/deps/librdkafka/packaging/tools/build-release-artifacts.sh +0 -0
  56. package/deps/librdkafka/packaging/tools/distro-build.sh +0 -0
  57. package/deps/librdkafka/packaging/tools/gh-release-checksums.py +0 -0
  58. package/deps/librdkafka/packaging/tools/rdutcoverage.sh +0 -0
  59. package/deps/librdkafka/packaging/tools/run-in-docker.sh +28 -0
  60. package/deps/librdkafka/packaging/tools/run-integration-tests.sh +31 -0
  61. package/deps/librdkafka/packaging/tools/run-style-check.sh +4 -0
  62. package/deps/librdkafka/packaging/tools/style-format.sh +6 -5
  63. package/deps/librdkafka/packaging/tools/update_rpcs_max_versions.py +100 -0
  64. package/deps/librdkafka/service.yml +154 -0
  65. package/deps/librdkafka/src/generate_proto.sh +0 -0
  66. package/deps/librdkafka/src/rd.h +145 -2
  67. package/deps/librdkafka/src/rdaddr.h +6 -7
  68. package/deps/librdkafka/src/rdatomic.h +15 -11
  69. package/deps/librdkafka/src/rdavg.h +3 -2
  70. package/deps/librdkafka/src/rdbase64.c +33 -2
  71. package/deps/librdkafka/src/rdbase64.h +2 -0
  72. package/deps/librdkafka/src/rdendian.h +10 -9
  73. package/deps/librdkafka/src/rdhttp.c +418 -100
  74. package/deps/librdkafka/src/rdhttp.h +23 -5
  75. package/deps/librdkafka/src/rdkafka.c +299 -145
  76. package/deps/librdkafka/src/rdkafka.h +89 -6
  77. package/deps/librdkafka/src/rdkafka_admin.c +482 -105
  78. package/deps/librdkafka/src/rdkafka_admin.h +42 -0
  79. package/deps/librdkafka/src/rdkafka_broker.c +463 -312
  80. package/deps/librdkafka/src/rdkafka_broker.h +68 -10
  81. package/deps/librdkafka/src/rdkafka_buf.c +8 -5
  82. package/deps/librdkafka/src/rdkafka_buf.h +4 -3
  83. package/deps/librdkafka/src/rdkafka_cert.c +25 -1
  84. package/deps/librdkafka/src/rdkafka_cert.h +4 -3
  85. package/deps/librdkafka/src/rdkafka_cgrp.c +468 -143
  86. package/deps/librdkafka/src/rdkafka_cgrp.h +36 -2
  87. package/deps/librdkafka/src/rdkafka_conf.c +526 -102
  88. package/deps/librdkafka/src/rdkafka_conf.h +68 -4
  89. package/deps/librdkafka/src/rdkafka_feature.c +472 -35
  90. package/deps/librdkafka/src/rdkafka_feature.h +2 -0
  91. package/deps/librdkafka/src/rdkafka_fetcher.c +22 -2
  92. package/deps/librdkafka/src/rdkafka_fetcher.h +2 -2
  93. package/deps/librdkafka/src/rdkafka_int.h +84 -46
  94. package/deps/librdkafka/src/rdkafka_metadata.c +221 -136
  95. package/deps/librdkafka/src/rdkafka_metadata.h +15 -11
  96. package/deps/librdkafka/src/rdkafka_metadata_cache.c +279 -73
  97. package/deps/librdkafka/src/rdkafka_mock.c +795 -19
  98. package/deps/librdkafka/src/rdkafka_mock.h +119 -0
  99. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1298 -132
  100. package/deps/librdkafka/src/rdkafka_mock_handlers.c +345 -49
  101. package/deps/librdkafka/src/rdkafka_mock_int.h +180 -61
  102. package/deps/librdkafka/src/rdkafka_msg.c +35 -19
  103. package/deps/librdkafka/src/rdkafka_msg.h +3 -3
  104. package/deps/librdkafka/src/rdkafka_msgset_writer.c +14 -10
  105. package/deps/librdkafka/src/rdkafka_offset.c +33 -5
  106. package/deps/librdkafka/src/rdkafka_op.c +1 -1
  107. package/deps/librdkafka/src/rdkafka_op.h +45 -21
  108. package/deps/librdkafka/src/rdkafka_partition.c +118 -23
  109. package/deps/librdkafka/src/rdkafka_partition.h +16 -7
  110. package/deps/librdkafka/src/rdkafka_proto.h +4 -1
  111. package/deps/librdkafka/src/rdkafka_protocol.h +1 -0
  112. package/deps/librdkafka/src/rdkafka_queue.c +125 -48
  113. package/deps/librdkafka/src/rdkafka_queue.h +19 -0
  114. package/deps/librdkafka/src/rdkafka_range_assignor.c +1 -1
  115. package/deps/librdkafka/src/rdkafka_request.c +336 -64
  116. package/deps/librdkafka/src/rdkafka_request.h +24 -1
  117. package/deps/librdkafka/src/rdkafka_sasl.c +5 -3
  118. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +14 -10
  119. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1166 -89
  120. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +13 -3
  121. package/deps/librdkafka/src/rdkafka_sasl_plain.c +3 -3
  122. package/deps/librdkafka/src/rdkafka_sasl_scram.c +4 -3
  123. package/deps/librdkafka/src/rdkafka_ssl.c +306 -81
  124. package/deps/librdkafka/src/rdkafka_ssl.h +22 -0
  125. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4 -0
  126. package/deps/librdkafka/src/rdkafka_telemetry.c +80 -23
  127. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +5 -1
  128. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +20 -20
  129. package/deps/librdkafka/src/rdkafka_topic.c +220 -137
  130. package/deps/librdkafka/src/rdkafka_topic.h +14 -8
  131. package/deps/librdkafka/src/rdkafka_transport.c +59 -47
  132. package/deps/librdkafka/src/rdkafka_transport.h +5 -0
  133. package/deps/librdkafka/src/rdkafka_txnmgr.c +6 -1
  134. package/deps/librdkafka/src/rdmap.c +6 -1
  135. package/deps/librdkafka/src/rdrand.c +11 -1
  136. package/deps/librdkafka/src/rdtime.h +48 -17
  137. package/deps/librdkafka/src/rdunittest.c +42 -25
  138. package/deps/librdkafka/src/rdunittest.h +2 -0
  139. package/deps/librdkafka/src/rdwin32.h +1 -1
  140. package/deps/librdkafka/src/regexp.c +47 -42
  141. package/deps/librdkafka/src/tinycthread_extra.c +45 -21
  142. package/deps/librdkafka/src/tinycthread_extra.h +7 -3
  143. package/deps/librdkafka/src-cpp/rdkafkacpp.h +28 -2
  144. package/deps/librdkafka/tests/0001-multiobj.c +4 -0
  145. package/deps/librdkafka/tests/0004-conf.c +103 -36
  146. package/deps/librdkafka/tests/0009-mock_cluster.c +1 -0
  147. package/deps/librdkafka/tests/0011-produce_batch.c +2 -1
  148. package/deps/librdkafka/tests/0016-client_swname.c +18 -3
  149. package/deps/librdkafka/tests/0017-compression.c +6 -8
  150. package/deps/librdkafka/tests/0018-cgrp_term.c +8 -3
  151. package/deps/librdkafka/tests/0021-rkt_destroy.c +1 -0
  152. package/deps/librdkafka/tests/0022-consume_batch.c +4 -1
  153. package/deps/librdkafka/tests/0026-consume_pause.c +12 -14
  154. package/deps/librdkafka/tests/0028-long_topicnames.c +1 -1
  155. package/deps/librdkafka/tests/0029-assign_offset.c +6 -8
  156. package/deps/librdkafka/tests/0030-offset_commit.c +2 -2
  157. package/deps/librdkafka/tests/0033-regex_subscribe.c +33 -17
  158. package/deps/librdkafka/tests/0034-offset_reset.c +25 -4
  159. package/deps/librdkafka/tests/0036-partial_fetch.c +1 -0
  160. package/deps/librdkafka/tests/0038-performance.c +1 -0
  161. package/deps/librdkafka/tests/0040-io_event.c +11 -5
  162. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +1 -0
  163. package/deps/librdkafka/tests/0044-partition_cnt.c +3 -2
  164. package/deps/librdkafka/tests/0045-subscribe_update.c +284 -20
  165. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +1 -0
  166. package/deps/librdkafka/tests/0048-partitioner.c +1 -1
  167. package/deps/librdkafka/tests/0050-subscribe_adds.c +1 -0
  168. package/deps/librdkafka/tests/0051-assign_adds.c +1 -0
  169. package/deps/librdkafka/tests/0052-msg_timestamps.c +22 -4
  170. package/deps/librdkafka/tests/0053-stats_cb.cpp +12 -20
  171. package/deps/librdkafka/tests/0055-producer_latency.c +176 -3
  172. package/deps/librdkafka/tests/0056-balanced_group_mt.c +6 -2
  173. package/deps/librdkafka/tests/0058-log.cpp +3 -3
  174. package/deps/librdkafka/tests/0059-bsearch.cpp +8 -4
  175. package/deps/librdkafka/tests/0061-consumer_lag.cpp +25 -5
  176. package/deps/librdkafka/tests/0067-empty_topic.cpp +3 -0
  177. package/deps/librdkafka/tests/0068-produce_timeout.c +21 -23
  178. package/deps/librdkafka/tests/0069-consumer_add_parts.c +3 -7
  179. package/deps/librdkafka/tests/0075-retry.c +38 -1
  180. package/deps/librdkafka/tests/0076-produce_retry.c +2 -0
  181. package/deps/librdkafka/tests/0077-compaction.c +8 -2
  182. package/deps/librdkafka/tests/0080-admin_ut.c +34 -4
  183. package/deps/librdkafka/tests/0081-admin.c +455 -155
  184. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +4 -0
  185. package/deps/librdkafka/tests/0083-cb_event.c +8 -3
  186. package/deps/librdkafka/tests/0084-destroy_flags.c +14 -18
  187. package/deps/librdkafka/tests/0085-headers.cpp +4 -0
  188. package/deps/librdkafka/tests/0086-purge.c +40 -7
  189. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +1 -1
  190. package/deps/librdkafka/tests/0089-max_poll_interval.c +12 -7
  191. package/deps/librdkafka/tests/0090-idempotence.c +1 -2
  192. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +5 -7
  193. package/deps/librdkafka/tests/0092-mixed_msgver.c +6 -0
  194. package/deps/librdkafka/tests/0093-holb.c +8 -5
  195. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +1 -0
  196. package/deps/librdkafka/tests/0097-ssl_verify.cpp +262 -70
  197. package/deps/librdkafka/tests/0098-consumer-txn.cpp +16 -16
  198. package/deps/librdkafka/tests/0099-commit_metadata.c +1 -1
  199. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +1 -1
  200. package/deps/librdkafka/tests/0102-static_group_rebalance.c +317 -16
  201. package/deps/librdkafka/tests/0103-transactions.c +7 -7
  202. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +1 -0
  203. package/deps/librdkafka/tests/0105-transactions_mock.c +18 -11
  204. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +35 -14
  205. package/deps/librdkafka/tests/0107-topic_recreate.c +2 -2
  206. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +86 -26
  207. package/deps/librdkafka/tests/0110-batch_size.cpp +1 -2
  208. package/deps/librdkafka/tests/0112-assign_unknown_part.c +3 -14
  209. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +248 -104
  210. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +3 -3
  211. package/deps/librdkafka/tests/0115-producer_auth.cpp +4 -1
  212. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +7 -5
  213. package/deps/librdkafka/tests/0117-mock_errors.c +15 -5
  214. package/deps/librdkafka/tests/0118-commit_rebalance.c +41 -8
  215. package/deps/librdkafka/tests/0119-consumer_auth.cpp +20 -1
  216. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +8 -3
  217. package/deps/librdkafka/tests/0125-immediate_flush.c +1 -1
  218. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +323 -8
  219. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +1 -0
  220. package/deps/librdkafka/tests/0132-strategy_ordering.c +12 -4
  221. package/deps/librdkafka/tests/0133-ssl_keys.c +28 -6
  222. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +24 -14
  223. package/deps/librdkafka/tests/0139-offset_validation_mock.c +510 -2
  224. package/deps/librdkafka/tests/0140-commit_metadata.cpp +1 -1
  225. package/deps/librdkafka/tests/0142-reauthentication.c +37 -17
  226. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +39 -40
  227. package/deps/librdkafka/tests/0146-metadata_mock.c +76 -15
  228. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +952 -0
  229. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +563 -0
  230. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  231. package/deps/librdkafka/tests/0150-telemetry_mock.c +9 -6
  232. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  233. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  234. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  235. package/deps/librdkafka/tests/CMakeLists.txt +6 -0
  236. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +34 -9
  237. package/deps/librdkafka/tests/autotest.sh +0 -0
  238. package/deps/librdkafka/tests/broker_version_tests.py +0 -0
  239. package/deps/librdkafka/tests/buildbox.sh +0 -0
  240. package/deps/librdkafka/tests/cleanup-checker-tests.sh +0 -0
  241. package/deps/librdkafka/tests/cluster_testing.py +0 -0
  242. package/deps/librdkafka/tests/delete-test-topics.sh +0 -0
  243. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  244. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  245. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  246. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  247. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +42 -101
  248. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  249. package/deps/librdkafka/tests/fixtures/ssl/client2.key +41 -29
  250. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +112 -37
  251. package/deps/librdkafka/tests/gen-ssl-certs.sh +0 -0
  252. package/deps/librdkafka/tests/interactive_broker_version.py +0 -0
  253. package/deps/librdkafka/tests/java/Makefile +2 -1
  254. package/deps/librdkafka/tests/java/run-class.sh +0 -0
  255. package/deps/librdkafka/tests/lz4_manual_test.sh +0 -0
  256. package/deps/librdkafka/tests/multi-broker-version-test.sh +0 -0
  257. package/deps/librdkafka/tests/parse-refcnt.sh +0 -0
  258. package/deps/librdkafka/tests/performance_plot.py +0 -0
  259. package/deps/librdkafka/tests/requirements.txt +1 -1
  260. package/deps/librdkafka/tests/run-all-tests.sh +79 -0
  261. package/deps/librdkafka/tests/run-consumer-tests.sh +0 -0
  262. package/deps/librdkafka/tests/run-producer-tests.sh +0 -0
  263. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  264. package/deps/librdkafka/tests/run-test.sh +0 -0
  265. package/deps/librdkafka/tests/sasl_test.py +0 -0
  266. package/deps/librdkafka/tests/test.c +478 -18
  267. package/deps/librdkafka/tests/test.h +62 -13
  268. package/deps/librdkafka/tests/testcpp.cpp +6 -1
  269. package/deps/librdkafka/tests/testcpp.h +30 -2
  270. package/deps/librdkafka/tests/testshared.h +16 -2
  271. package/deps/librdkafka/tests/tools/stats/graph.py +0 -0
  272. package/deps/librdkafka/tests/tools/stats/to_csv.py +1 -1
  273. package/deps/librdkafka/tests/trivup/trivup-0.14.0.tar.gz +0 -0
  274. package/deps/librdkafka/tests/until-fail.sh +0 -0
  275. package/deps/librdkafka/tests/xxxx-metadata.cpp +1 -1
  276. package/deps/librdkafka/vcpkg.json +1 -1
  277. package/deps/librdkafka/win32/setup-msys2.ps1 +24 -8
  278. package/deps/librdkafka/win32/setup-vcpkg.ps1 +28 -6
  279. package/deps/librdkafka/win32/tests/tests.vcxproj +6 -0
  280. package/deps/librdkafka/win32/wingetopt.h +5 -4
  281. package/errors.d.ts +27 -1
  282. package/lib/error.js +29 -3
  283. package/make_docs.sh +0 -0
  284. package/package.json +10 -11
  285. package/run_docker.sh +0 -0
  286. package/src/callbacks.cc +6 -9
  287. package/src/callbacks.h +1 -5
  288. package/test/kafka-consumer-worker.js +10 -7
  289. package/test/kafka-consumer.spec.js +32 -21
  290. package/deps/librdkafka/.semaphore/project.yml +0 -43
  291. package/deps/librdkafka/.semaphore/project_public.yml +0 -20
  292. package/deps/librdkafka/win32/install-openssl.ps1 +0 -33
@@ -37,19 +37,21 @@
37
37
  #include "rdkafka_mock_int.h"
38
38
 
39
39
 
40
- static const char *rd_kafka_mock_cgrp_state_names[] = {
40
+ static const char *rd_kafka_mock_cgrp_classic_state_names[] = {
41
41
  "Empty", "Joining", "Syncing", "Rebalancing", "Up"};
42
42
 
43
43
 
44
- static void rd_kafka_mock_cgrp_rebalance(rd_kafka_mock_cgrp_t *mcgrp,
45
- const char *reason);
46
44
  static void
47
- rd_kafka_mock_cgrp_member_destroy(rd_kafka_mock_cgrp_t *mcgrp,
48
- rd_kafka_mock_cgrp_member_t *member);
45
+ rd_kafka_mock_cgrp_classic_rebalance(rd_kafka_mock_cgrp_classic_t *mcgrp,
46
+ const char *reason);
47
+ static void rd_kafka_mock_cgrp_classic_member_destroy(
48
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
49
+ rd_kafka_mock_cgrp_classic_member_t *member);
49
50
 
50
- static void rd_kafka_mock_cgrp_set_state(rd_kafka_mock_cgrp_t *mcgrp,
51
- unsigned int new_state,
52
- const char *reason) {
51
+ static void
52
+ rd_kafka_mock_cgrp_classic_set_state(rd_kafka_mock_cgrp_classic_t *mcgrp,
53
+ unsigned int new_state,
54
+ const char *reason) {
53
55
  if (mcgrp->state == new_state)
54
56
  return;
55
57
 
@@ -57,8 +59,8 @@ static void rd_kafka_mock_cgrp_set_state(rd_kafka_mock_cgrp_t *mcgrp,
57
59
  "Mock consumer group %s with %d member(s) "
58
60
  "changing state %s -> %s: %s",
59
61
  mcgrp->id, mcgrp->member_cnt,
60
- rd_kafka_mock_cgrp_state_names[mcgrp->state],
61
- rd_kafka_mock_cgrp_state_names[new_state], reason);
62
+ rd_kafka_mock_cgrp_classic_state_names[mcgrp->state],
63
+ rd_kafka_mock_cgrp_classic_state_names[new_state], reason);
62
64
 
63
65
  mcgrp->state = new_state;
64
66
  }
@@ -67,8 +69,9 @@ static void rd_kafka_mock_cgrp_set_state(rd_kafka_mock_cgrp_t *mcgrp,
67
69
  /**
68
70
  * @brief Mark member as active (restart session timer)
69
71
  */
70
- void rd_kafka_mock_cgrp_member_active(rd_kafka_mock_cgrp_t *mcgrp,
71
- rd_kafka_mock_cgrp_member_t *member) {
72
+ void rd_kafka_mock_cgrp_classic_member_active(
73
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
74
+ rd_kafka_mock_cgrp_classic_member_t *member) {
72
75
  rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
73
76
  "Marking mock consumer group member %s as active",
74
77
  member->id);
@@ -81,11 +84,11 @@ void rd_kafka_mock_cgrp_member_active(rd_kafka_mock_cgrp_t *mcgrp,
81
84
  *
82
85
  * @param member may be NULL.
83
86
  */
84
- rd_kafka_resp_err_t
85
- rd_kafka_mock_cgrp_check_state(rd_kafka_mock_cgrp_t *mcgrp,
86
- rd_kafka_mock_cgrp_member_t *member,
87
- const rd_kafka_buf_t *request,
88
- int32_t generation_id) {
87
+ rd_kafka_resp_err_t rd_kafka_mock_cgrp_classic_check_state(
88
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
89
+ rd_kafka_mock_cgrp_classic_member_t *member,
90
+ const rd_kafka_buf_t *request,
91
+ int32_t generation_id) {
89
92
  int16_t ApiKey = request->rkbuf_reqhdr.ApiKey;
90
93
  rd_bool_t has_generation_id = ApiKey == RD_KAFKAP_SyncGroup ||
91
94
  ApiKey == RD_KAFKAP_Heartbeat ||
@@ -142,9 +145,9 @@ rd_kafka_mock_cgrp_check_state(rd_kafka_mock_cgrp_t *mcgrp,
142
145
  /**
143
146
  * @brief Set a member's assignment (from leader's SyncGroupRequest)
144
147
  */
145
- void rd_kafka_mock_cgrp_member_assignment_set(
146
- rd_kafka_mock_cgrp_t *mcgrp,
147
- rd_kafka_mock_cgrp_member_t *member,
148
+ void rd_kafka_mock_cgrp_classic_member_assignment_set(
149
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
150
+ rd_kafka_mock_cgrp_classic_member_t *member,
148
151
  const rd_kafkap_bytes_t *Metadata) {
149
152
  if (member->assignment) {
150
153
  rd_assert(mcgrp->assignment_cnt > 0);
@@ -163,9 +166,10 @@ void rd_kafka_mock_cgrp_member_assignment_set(
163
166
  /**
164
167
  * @brief Sync done (successfully) or failed, send responses back to members.
165
168
  */
166
- static void rd_kafka_mock_cgrp_sync_done(rd_kafka_mock_cgrp_t *mcgrp,
167
- rd_kafka_resp_err_t err) {
168
- rd_kafka_mock_cgrp_member_t *member;
169
+ static void
170
+ rd_kafka_mock_cgrp_classic_sync_done(rd_kafka_mock_cgrp_classic_t *mcgrp,
171
+ rd_kafka_resp_err_t err) {
172
+ rd_kafka_mock_cgrp_classic_member_t *member;
169
173
 
170
174
  TAILQ_FOREACH(member, &mcgrp->members, link) {
171
175
  rd_kafka_buf_t *resp;
@@ -181,7 +185,8 @@ static void rd_kafka_mock_cgrp_sync_done(rd_kafka_mock_cgrp_t *mcgrp,
181
185
  resp, !err ? member->assignment : NULL);
182
186
  }
183
187
 
184
- rd_kafka_mock_cgrp_member_assignment_set(mcgrp, member, NULL);
188
+ rd_kafka_mock_cgrp_classic_member_assignment_set(mcgrp, member,
189
+ NULL);
185
190
 
186
191
  if (member->conn) {
187
192
  rd_kafka_mock_connection_set_blocking(member->conn,
@@ -201,20 +206,21 @@ static void rd_kafka_mock_cgrp_sync_done(rd_kafka_mock_cgrp_t *mcgrp,
201
206
  * @brief Check if all members have sent SyncGroupRequests, if so, propagate
202
207
  * assignment to members.
203
208
  */
204
- static void rd_kafka_mock_cgrp_sync_check(rd_kafka_mock_cgrp_t *mcgrp) {
209
+ static void
210
+ rd_kafka_mock_cgrp_classic_sync_check(rd_kafka_mock_cgrp_classic_t *mcgrp) {
205
211
 
206
212
  rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
207
213
  "Mock consumer group %s: awaiting %d/%d syncing members "
208
214
  "in state %s",
209
215
  mcgrp->id, mcgrp->assignment_cnt, mcgrp->member_cnt,
210
- rd_kafka_mock_cgrp_state_names[mcgrp->state]);
216
+ rd_kafka_mock_cgrp_classic_state_names[mcgrp->state]);
211
217
 
212
218
  if (mcgrp->assignment_cnt < mcgrp->member_cnt)
213
219
  return;
214
220
 
215
- rd_kafka_mock_cgrp_sync_done(mcgrp, RD_KAFKA_RESP_ERR_NO_ERROR);
216
- rd_kafka_mock_cgrp_set_state(mcgrp, RD_KAFKA_MOCK_CGRP_STATE_UP,
217
- "all members synced");
221
+ rd_kafka_mock_cgrp_classic_sync_done(mcgrp, RD_KAFKA_RESP_ERR_NO_ERROR);
222
+ rd_kafka_mock_cgrp_classic_set_state(mcgrp, RD_KAFKA_MOCK_CGRP_STATE_UP,
223
+ "all members synced");
218
224
  }
219
225
 
220
226
 
@@ -223,16 +229,16 @@ static void rd_kafka_mock_cgrp_sync_check(rd_kafka_mock_cgrp_t *mcgrp) {
223
229
  * which will be sent when the all group member SyncGroupRequest are
224
230
  * received.
225
231
  */
226
- rd_kafka_resp_err_t
227
- rd_kafka_mock_cgrp_member_sync_set(rd_kafka_mock_cgrp_t *mcgrp,
228
- rd_kafka_mock_cgrp_member_t *member,
229
- rd_kafka_mock_connection_t *mconn,
230
- rd_kafka_buf_t *resp) {
232
+ rd_kafka_resp_err_t rd_kafka_mock_cgrp_classic_member_sync_set(
233
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
234
+ rd_kafka_mock_cgrp_classic_member_t *member,
235
+ rd_kafka_mock_connection_t *mconn,
236
+ rd_kafka_buf_t *resp) {
231
237
 
232
238
  if (mcgrp->state != RD_KAFKA_MOCK_CGRP_STATE_SYNCING)
233
239
  return RD_KAFKA_RESP_ERR_REBALANCE_IN_PROGRESS; /* FIXME */
234
240
 
235
- rd_kafka_mock_cgrp_member_active(mcgrp, member);
241
+ rd_kafka_mock_cgrp_classic_member_active(mcgrp, member);
236
242
 
237
243
  rd_assert(!member->resp);
238
244
 
@@ -241,7 +247,7 @@ rd_kafka_mock_cgrp_member_sync_set(rd_kafka_mock_cgrp_t *mcgrp,
241
247
  rd_kafka_mock_connection_set_blocking(member->conn, rd_true);
242
248
 
243
249
  /* Check if all members now have an assignment, if so, send responses */
244
- rd_kafka_mock_cgrp_sync_check(mcgrp);
250
+ rd_kafka_mock_cgrp_classic_sync_check(mcgrp);
245
251
 
246
252
  return RD_KAFKA_RESP_ERR_NO_ERROR;
247
253
  }
@@ -250,16 +256,16 @@ rd_kafka_mock_cgrp_member_sync_set(rd_kafka_mock_cgrp_t *mcgrp,
250
256
  /**
251
257
  * @brief Member is explicitly leaving the group (through LeaveGroupRequest)
252
258
  */
253
- rd_kafka_resp_err_t
254
- rd_kafka_mock_cgrp_member_leave(rd_kafka_mock_cgrp_t *mcgrp,
255
- rd_kafka_mock_cgrp_member_t *member) {
259
+ rd_kafka_resp_err_t rd_kafka_mock_cgrp_classic_member_leave(
260
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
261
+ rd_kafka_mock_cgrp_classic_member_t *member) {
256
262
 
257
263
  rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
258
264
  "Member %s is leaving group %s", member->id, mcgrp->id);
259
265
 
260
- rd_kafka_mock_cgrp_member_destroy(mcgrp, member);
266
+ rd_kafka_mock_cgrp_classic_member_destroy(mcgrp, member);
261
267
 
262
- rd_kafka_mock_cgrp_rebalance(mcgrp, "explicit member leave");
268
+ rd_kafka_mock_cgrp_classic_rebalance(mcgrp, "explicit member leave");
263
269
 
264
270
  return RD_KAFKA_RESP_ERR_NO_ERROR;
265
271
  }
@@ -267,8 +273,9 @@ rd_kafka_mock_cgrp_member_leave(rd_kafka_mock_cgrp_t *mcgrp,
267
273
  /**
268
274
  * @brief Destroys/frees an array of protocols, including the array itself.
269
275
  */
270
- void rd_kafka_mock_cgrp_protos_destroy(rd_kafka_mock_cgrp_proto_t *protos,
271
- int proto_cnt) {
276
+ void rd_kafka_mock_cgrp_classic_protos_destroy(
277
+ rd_kafka_mock_cgrp_classic_proto_t *protos,
278
+ int proto_cnt) {
272
279
  int i;
273
280
 
274
281
  for (i = 0; i < proto_cnt; i++) {
@@ -280,15 +287,16 @@ void rd_kafka_mock_cgrp_protos_destroy(rd_kafka_mock_cgrp_proto_t *protos,
280
287
  rd_free(protos);
281
288
  }
282
289
 
283
- static void
284
- rd_kafka_mock_cgrp_rebalance_timer_restart(rd_kafka_mock_cgrp_t *mcgrp,
285
- int timeout_ms);
290
+ static void rd_kafka_mock_cgrp_classic_rebalance_timer_restart(
291
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
292
+ int timeout_ms);
286
293
 
287
294
  /**
288
295
  * @brief Elect consumer group leader and send JoinGroup responses
289
296
  */
290
- static void rd_kafka_mock_cgrp_elect_leader(rd_kafka_mock_cgrp_t *mcgrp) {
291
- rd_kafka_mock_cgrp_member_t *member;
297
+ static void
298
+ rd_kafka_mock_cgrp_classic_elect_leader(rd_kafka_mock_cgrp_classic_t *mcgrp) {
299
+ rd_kafka_mock_cgrp_classic_member_t *member;
292
300
 
293
301
  rd_assert(mcgrp->state == RD_KAFKA_MOCK_CGRP_STATE_JOINING);
294
302
  rd_assert(!TAILQ_EMPTY(&mcgrp->members));
@@ -331,7 +339,7 @@ static void rd_kafka_mock_cgrp_elect_leader(rd_kafka_mock_cgrp_t *mcgrp) {
331
339
  rd_bool_t is_leader = member == mcgrp->leader;
332
340
  int member_cnt = is_leader ? mcgrp->member_cnt : 0;
333
341
  rd_kafka_buf_t *resp;
334
- rd_kafka_mock_cgrp_member_t *member2;
342
+ rd_kafka_mock_cgrp_classic_member_t *member2;
335
343
  rd_kafka_mock_connection_t *mconn;
336
344
 
337
345
  /* Member connection has been closed, it will eventually
@@ -373,7 +381,7 @@ static void rd_kafka_mock_cgrp_elect_leader(rd_kafka_mock_cgrp_t *mcgrp) {
373
381
  /* Mark each member as active to avoid them timing out
374
382
  * at the same time as a JoinGroup handler that blocks
375
383
  * session.timeout.ms to elect a leader. */
376
- rd_kafka_mock_cgrp_member_active(mcgrp, member);
384
+ rd_kafka_mock_cgrp_classic_member_active(mcgrp, member);
377
385
 
378
386
  rd_kafka_mock_connection_set_blocking(mconn, rd_false);
379
387
  rd_kafka_mock_connection_send_response(mconn, resp);
@@ -381,20 +389,22 @@ static void rd_kafka_mock_cgrp_elect_leader(rd_kafka_mock_cgrp_t *mcgrp) {
381
389
 
382
390
  mcgrp->last_member_cnt = mcgrp->member_cnt;
383
391
 
384
- rd_kafka_mock_cgrp_set_state(mcgrp, RD_KAFKA_MOCK_CGRP_STATE_SYNCING,
385
- "leader elected, waiting for all "
386
- "members to sync");
392
+ rd_kafka_mock_cgrp_classic_set_state(mcgrp,
393
+ RD_KAFKA_MOCK_CGRP_STATE_SYNCING,
394
+ "leader elected, waiting for all "
395
+ "members to sync");
387
396
 
388
- rd_kafka_mock_cgrp_rebalance_timer_restart(mcgrp,
389
- mcgrp->session_timeout_ms);
397
+ rd_kafka_mock_cgrp_classic_rebalance_timer_restart(
398
+ mcgrp, mcgrp->session_timeout_ms);
390
399
  }
391
400
 
392
401
 
393
402
  /**
394
403
  * @brief Trigger group rebalance.
395
404
  */
396
- static void rd_kafka_mock_cgrp_rebalance(rd_kafka_mock_cgrp_t *mcgrp,
397
- const char *reason) {
405
+ static void
406
+ rd_kafka_mock_cgrp_classic_rebalance(rd_kafka_mock_cgrp_classic_t *mcgrp,
407
+ const char *reason) {
398
408
  int timeout_ms;
399
409
 
400
410
  if (mcgrp->state == RD_KAFKA_MOCK_CGRP_STATE_JOINING)
@@ -418,21 +428,23 @@ static void rd_kafka_mock_cgrp_rebalance(rd_kafka_mock_cgrp_t *mcgrp,
418
428
 
419
429
  if (mcgrp->state == RD_KAFKA_MOCK_CGRP_STATE_SYNCING)
420
430
  /* Abort current Syncing state */
421
- rd_kafka_mock_cgrp_sync_done(
431
+ rd_kafka_mock_cgrp_classic_sync_done(
422
432
  mcgrp, RD_KAFKA_RESP_ERR_REBALANCE_IN_PROGRESS);
423
433
 
424
- rd_kafka_mock_cgrp_set_state(mcgrp, RD_KAFKA_MOCK_CGRP_STATE_JOINING,
425
- reason);
426
- rd_kafka_mock_cgrp_rebalance_timer_restart(mcgrp, timeout_ms);
434
+ rd_kafka_mock_cgrp_classic_set_state(
435
+ mcgrp, RD_KAFKA_MOCK_CGRP_STATE_JOINING, reason);
436
+ rd_kafka_mock_cgrp_classic_rebalance_timer_restart(mcgrp, timeout_ms);
427
437
  }
428
438
 
429
439
  /**
430
440
  * @brief Consumer group state machine triggered by timer events.
431
441
  */
432
- static void rd_kafka_mock_cgrp_fsm_timeout(rd_kafka_mock_cgrp_t *mcgrp) {
442
+ static void
443
+ rd_kafka_mock_cgrp_classic_fsm_timeout(rd_kafka_mock_cgrp_classic_t *mcgrp) {
433
444
  rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
434
445
  "Mock consumer group %s FSM timeout in state %s",
435
- mcgrp->id, rd_kafka_mock_cgrp_state_names[mcgrp->state]);
446
+ mcgrp->id,
447
+ rd_kafka_mock_cgrp_classic_state_names[mcgrp->state]);
436
448
 
437
449
  switch (mcgrp->state) {
438
450
  case RD_KAFKA_MOCK_CGRP_STATE_EMPTY:
@@ -441,9 +453,9 @@ static void rd_kafka_mock_cgrp_fsm_timeout(rd_kafka_mock_cgrp_t *mcgrp) {
441
453
  case RD_KAFKA_MOCK_CGRP_STATE_JOINING:
442
454
  /* Timed out waiting for more members, elect a leader */
443
455
  if (mcgrp->member_cnt > 0)
444
- rd_kafka_mock_cgrp_elect_leader(mcgrp);
456
+ rd_kafka_mock_cgrp_classic_elect_leader(mcgrp);
445
457
  else
446
- rd_kafka_mock_cgrp_set_state(
458
+ rd_kafka_mock_cgrp_classic_set_state(
447
459
  mcgrp, RD_KAFKA_MOCK_CGRP_STATE_EMPTY,
448
460
  "no members joined");
449
461
  break;
@@ -452,20 +464,20 @@ static void rd_kafka_mock_cgrp_fsm_timeout(rd_kafka_mock_cgrp_t *mcgrp) {
452
464
  /* Timed out waiting for all members to sync */
453
465
 
454
466
  /* Send error response to all waiting members */
455
- rd_kafka_mock_cgrp_sync_done(
467
+ rd_kafka_mock_cgrp_classic_sync_done(
456
468
  mcgrp, RD_KAFKA_RESP_ERR_REBALANCE_IN_PROGRESS /* FIXME */);
457
469
 
458
- rd_kafka_mock_cgrp_set_state(
470
+ rd_kafka_mock_cgrp_classic_set_state(
459
471
  mcgrp, RD_KAFKA_MOCK_CGRP_STATE_REBALANCING,
460
472
  "timed out waiting for all members to synchronize");
461
473
  break;
462
474
 
463
475
  case RD_KAFKA_MOCK_CGRP_STATE_REBALANCING:
464
476
  /* Timed out waiting for all members to Leave or re-Join */
465
- rd_kafka_mock_cgrp_set_state(mcgrp,
466
- RD_KAFKA_MOCK_CGRP_STATE_JOINING,
467
- "timed out waiting for all "
468
- "members to re-Join or Leave");
477
+ rd_kafka_mock_cgrp_classic_set_state(
478
+ mcgrp, RD_KAFKA_MOCK_CGRP_STATE_JOINING,
479
+ "timed out waiting for all "
480
+ "members to re-Join or Leave");
469
481
  break;
470
482
 
471
483
  case RD_KAFKA_MOCK_CGRP_STATE_UP:
@@ -477,27 +489,27 @@ static void rd_kafka_mock_cgrp_fsm_timeout(rd_kafka_mock_cgrp_t *mcgrp) {
477
489
 
478
490
  static void rd_kafka_mcgrp_rebalance_timer_cb(rd_kafka_timers_t *rkts,
479
491
  void *arg) {
480
- rd_kafka_mock_cgrp_t *mcgrp = arg;
492
+ rd_kafka_mock_cgrp_classic_t *mcgrp = arg;
481
493
 
482
- rd_kafka_mock_cgrp_fsm_timeout(mcgrp);
494
+ rd_kafka_mock_cgrp_classic_fsm_timeout(mcgrp);
483
495
  }
484
496
 
485
497
 
486
498
  /**
487
499
  * @brief Restart the rebalance timer, postponing leader election.
488
500
  */
489
- static void
490
- rd_kafka_mock_cgrp_rebalance_timer_restart(rd_kafka_mock_cgrp_t *mcgrp,
491
- int timeout_ms) {
501
+ static void rd_kafka_mock_cgrp_classic_rebalance_timer_restart(
502
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
503
+ int timeout_ms) {
492
504
  rd_kafka_timer_start_oneshot(
493
505
  &mcgrp->cluster->timers, &mcgrp->rebalance_tmr, rd_true,
494
506
  timeout_ms * 1000, rd_kafka_mcgrp_rebalance_timer_cb, mcgrp);
495
507
  }
496
508
 
497
509
 
498
- static void
499
- rd_kafka_mock_cgrp_member_destroy(rd_kafka_mock_cgrp_t *mcgrp,
500
- rd_kafka_mock_cgrp_member_t *member) {
510
+ static void rd_kafka_mock_cgrp_classic_member_destroy(
511
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
512
+ rd_kafka_mock_cgrp_classic_member_t *member) {
501
513
  rd_assert(mcgrp->member_cnt > 0);
502
514
  TAILQ_REMOVE(&mcgrp->members, member, link);
503
515
  mcgrp->member_cnt--;
@@ -510,9 +522,10 @@ rd_kafka_mock_cgrp_member_destroy(rd_kafka_mock_cgrp_t *mcgrp,
510
522
  if (member->group_instance_id)
511
523
  rd_free(member->group_instance_id);
512
524
 
513
- rd_kafka_mock_cgrp_member_assignment_set(mcgrp, member, NULL);
525
+ rd_kafka_mock_cgrp_classic_member_assignment_set(mcgrp, member, NULL);
514
526
 
515
- rd_kafka_mock_cgrp_protos_destroy(member->protos, member->proto_cnt);
527
+ rd_kafka_mock_cgrp_classic_protos_destroy(member->protos,
528
+ member->proto_cnt);
516
529
 
517
530
  rd_free(member);
518
531
  }
@@ -521,13 +534,13 @@ rd_kafka_mock_cgrp_member_destroy(rd_kafka_mock_cgrp_t *mcgrp,
521
534
  /**
522
535
  * @brief Find member in group.
523
536
  */
524
- rd_kafka_mock_cgrp_member_t *
525
- rd_kafka_mock_cgrp_member_find(const rd_kafka_mock_cgrp_t *mcgrp,
526
- const rd_kafkap_str_t *MemberId) {
527
- const rd_kafka_mock_cgrp_member_t *member;
537
+ rd_kafka_mock_cgrp_classic_member_t *rd_kafka_mock_cgrp_classic_member_find(
538
+ const rd_kafka_mock_cgrp_classic_t *mcgrp,
539
+ const rd_kafkap_str_t *MemberId) {
540
+ const rd_kafka_mock_cgrp_classic_member_t *member;
528
541
  TAILQ_FOREACH(member, &mcgrp->members, link) {
529
542
  if (!rd_kafkap_str_cmp_str(MemberId, member->id))
530
- return (rd_kafka_mock_cgrp_member_t *)member;
543
+ return (rd_kafka_mock_cgrp_classic_member_t *)member;
531
544
  }
532
545
 
533
546
  return NULL;
@@ -537,25 +550,25 @@ rd_kafka_mock_cgrp_member_find(const rd_kafka_mock_cgrp_t *mcgrp,
537
550
  /**
538
551
  * @brief Update or add member to consumer group
539
552
  */
540
- rd_kafka_resp_err_t
541
- rd_kafka_mock_cgrp_member_add(rd_kafka_mock_cgrp_t *mcgrp,
542
- rd_kafka_mock_connection_t *mconn,
543
- rd_kafka_buf_t *resp,
544
- const rd_kafkap_str_t *MemberId,
545
- const rd_kafkap_str_t *ProtocolType,
546
- const rd_kafkap_str_t *GroupInstanceId,
547
- rd_kafka_mock_cgrp_proto_t *protos,
548
- int proto_cnt,
549
- int session_timeout_ms) {
550
- rd_kafka_mock_cgrp_member_t *member;
553
+ rd_kafka_resp_err_t rd_kafka_mock_cgrp_classic_member_add(
554
+ rd_kafka_mock_cgrp_classic_t *mcgrp,
555
+ rd_kafka_mock_connection_t *mconn,
556
+ rd_kafka_buf_t *resp,
557
+ const rd_kafkap_str_t *MemberId,
558
+ const rd_kafkap_str_t *ProtocolType,
559
+ const rd_kafkap_str_t *GroupInstanceId,
560
+ rd_kafka_mock_cgrp_classic_proto_t *protos,
561
+ int proto_cnt,
562
+ int session_timeout_ms) {
563
+ rd_kafka_mock_cgrp_classic_member_t *member;
551
564
  rd_kafka_resp_err_t err;
552
565
 
553
- err = rd_kafka_mock_cgrp_check_state(mcgrp, NULL, resp, -1);
566
+ err = rd_kafka_mock_cgrp_classic_check_state(mcgrp, NULL, resp, -1);
554
567
  if (err)
555
568
  return err;
556
569
 
557
570
  /* Find member */
558
- member = rd_kafka_mock_cgrp_member_find(mcgrp, MemberId);
571
+ member = rd_kafka_mock_cgrp_classic_member_find(mcgrp, MemberId);
559
572
  if (!member) {
560
573
  /* Not found, add member */
561
574
  member = rd_calloc(1, sizeof(*member));
@@ -577,20 +590,20 @@ rd_kafka_mock_cgrp_member_add(rd_kafka_mock_cgrp_t *mcgrp,
577
590
  }
578
591
 
579
592
  if (mcgrp->state != RD_KAFKA_MOCK_CGRP_STATE_JOINING)
580
- rd_kafka_mock_cgrp_rebalance(mcgrp, "member join");
593
+ rd_kafka_mock_cgrp_classic_rebalance(mcgrp, "member join");
581
594
 
582
595
  mcgrp->session_timeout_ms = session_timeout_ms;
583
596
 
584
597
  if (member->protos)
585
- rd_kafka_mock_cgrp_protos_destroy(member->protos,
586
- member->proto_cnt);
598
+ rd_kafka_mock_cgrp_classic_protos_destroy(member->protos,
599
+ member->proto_cnt);
587
600
  member->protos = protos;
588
601
  member->proto_cnt = proto_cnt;
589
602
 
590
603
  rd_assert(!member->resp);
591
604
  member->resp = resp;
592
605
  member->conn = mconn;
593
- rd_kafka_mock_cgrp_member_active(mcgrp, member);
606
+ rd_kafka_mock_cgrp_classic_member_active(mcgrp, member);
594
607
 
595
608
  return RD_KAFKA_RESP_ERR_NO_ERROR;
596
609
  }
@@ -598,10 +611,10 @@ rd_kafka_mock_cgrp_member_add(rd_kafka_mock_cgrp_t *mcgrp,
598
611
  /**
599
612
  * @brief Check if any members have exceeded the session timeout.
600
613
  */
601
- static void rd_kafka_mock_cgrp_session_tmr_cb(rd_kafka_timers_t *rkts,
602
- void *arg) {
603
- rd_kafka_mock_cgrp_t *mcgrp = arg;
604
- rd_kafka_mock_cgrp_member_t *member, *tmp;
614
+ static void rd_kafka_mock_cgrp_classic_session_tmr_cb(rd_kafka_timers_t *rkts,
615
+ void *arg) {
616
+ rd_kafka_mock_cgrp_classic_t *mcgrp = arg;
617
+ rd_kafka_mock_cgrp_classic_member_t *member, *tmp;
605
618
  rd_ts_t now = rd_clock();
606
619
  int timeout_cnt = 0;
607
620
 
@@ -615,19 +628,19 @@ static void rd_kafka_mock_cgrp_session_tmr_cb(rd_kafka_timers_t *rkts,
615
628
  "Member %s session timed out for group %s",
616
629
  member->id, mcgrp->id);
617
630
 
618
- rd_kafka_mock_cgrp_member_destroy(mcgrp, member);
631
+ rd_kafka_mock_cgrp_classic_member_destroy(mcgrp, member);
619
632
  timeout_cnt++;
620
633
  }
621
634
 
622
635
  if (timeout_cnt)
623
- rd_kafka_mock_cgrp_rebalance(mcgrp, "member timeout");
636
+ rd_kafka_mock_cgrp_classic_rebalance(mcgrp, "member timeout");
624
637
  }
625
638
 
626
639
 
627
- void rd_kafka_mock_cgrp_destroy(rd_kafka_mock_cgrp_t *mcgrp) {
628
- rd_kafka_mock_cgrp_member_t *member;
640
+ void rd_kafka_mock_cgrp_classic_destroy(rd_kafka_mock_cgrp_classic_t *mcgrp) {
641
+ rd_kafka_mock_cgrp_classic_member_t *member;
629
642
 
630
- TAILQ_REMOVE(&mcgrp->cluster->cgrps, mcgrp, link);
643
+ TAILQ_REMOVE(&mcgrp->cluster->cgrps_classic, mcgrp, link);
631
644
 
632
645
  rd_kafka_timer_stop(&mcgrp->cluster->timers, &mcgrp->rebalance_tmr,
633
646
  rd_true);
@@ -638,15 +651,16 @@ void rd_kafka_mock_cgrp_destroy(rd_kafka_mock_cgrp_t *mcgrp) {
638
651
  if (mcgrp->protocol_name)
639
652
  rd_free(mcgrp->protocol_name);
640
653
  while ((member = TAILQ_FIRST(&mcgrp->members)))
641
- rd_kafka_mock_cgrp_member_destroy(mcgrp, member);
654
+ rd_kafka_mock_cgrp_classic_member_destroy(mcgrp, member);
642
655
  rd_free(mcgrp);
643
656
  }
644
657
 
645
658
 
646
- rd_kafka_mock_cgrp_t *rd_kafka_mock_cgrp_find(rd_kafka_mock_cluster_t *mcluster,
647
- const rd_kafkap_str_t *GroupId) {
648
- rd_kafka_mock_cgrp_t *mcgrp;
649
- TAILQ_FOREACH(mcgrp, &mcluster->cgrps, link) {
659
+ rd_kafka_mock_cgrp_classic_t *
660
+ rd_kafka_mock_cgrp_classic_find(rd_kafka_mock_cluster_t *mcluster,
661
+ const rd_kafkap_str_t *GroupId) {
662
+ rd_kafka_mock_cgrp_classic_t *mcgrp;
663
+ TAILQ_FOREACH(mcgrp, &mcluster->cgrps_classic, link) {
650
664
  if (!rd_kafkap_str_cmp_str(GroupId, mcgrp->id))
651
665
  return mcgrp;
652
666
  }
@@ -656,15 +670,15 @@ rd_kafka_mock_cgrp_t *rd_kafka_mock_cgrp_find(rd_kafka_mock_cluster_t *mcluster,
656
670
 
657
671
 
658
672
  /**
659
- * @brief Find or create a consumer group
673
+ * @brief Find or create a classic consumer group
660
674
  */
661
- rd_kafka_mock_cgrp_t *
662
- rd_kafka_mock_cgrp_get(rd_kafka_mock_cluster_t *mcluster,
663
- const rd_kafkap_str_t *GroupId,
664
- const rd_kafkap_str_t *ProtocolType) {
665
- rd_kafka_mock_cgrp_t *mcgrp;
675
+ rd_kafka_mock_cgrp_classic_t *
676
+ rd_kafka_mock_cgrp_classic_get(rd_kafka_mock_cluster_t *mcluster,
677
+ const rd_kafkap_str_t *GroupId,
678
+ const rd_kafkap_str_t *ProtocolType) {
679
+ rd_kafka_mock_cgrp_classic_t *mcgrp;
666
680
 
667
- mcgrp = rd_kafka_mock_cgrp_find(mcluster, GroupId);
681
+ mcgrp = rd_kafka_mock_cgrp_classic_find(mcluster, GroupId);
668
682
  if (mcgrp)
669
683
  return mcgrp;
670
684
 
@@ -679,24 +693,25 @@ rd_kafka_mock_cgrp_get(rd_kafka_mock_cluster_t *mcluster,
679
693
  TAILQ_INIT(&mcgrp->members);
680
694
  rd_kafka_timer_start(&mcluster->timers, &mcgrp->session_tmr,
681
695
  1000 * 1000 /*1s*/,
682
- rd_kafka_mock_cgrp_session_tmr_cb, mcgrp);
696
+ rd_kafka_mock_cgrp_classic_session_tmr_cb, mcgrp);
683
697
 
684
- TAILQ_INSERT_TAIL(&mcluster->cgrps, mcgrp, link);
698
+ TAILQ_INSERT_TAIL(&mcluster->cgrps_classic, mcgrp, link);
685
699
 
686
700
  return mcgrp;
687
701
  }
688
702
 
689
703
 
690
704
  /**
691
- * @brief A client connection closed, check if any cgrp has any state
705
+ * @brief A client connection closed, check if any classic cgrp has any state
692
706
  * for this connection that needs to be cleared.
693
707
  */
694
- void rd_kafka_mock_cgrps_connection_closed(rd_kafka_mock_cluster_t *mcluster,
695
- rd_kafka_mock_connection_t *mconn) {
696
- rd_kafka_mock_cgrp_t *mcgrp;
708
+ void rd_kafka_mock_cgrps_classic_connection_closed(
709
+ rd_kafka_mock_cluster_t *mcluster,
710
+ rd_kafka_mock_connection_t *mconn) {
711
+ rd_kafka_mock_cgrp_classic_t *mcgrp;
697
712
 
698
- TAILQ_FOREACH(mcgrp, &mcluster->cgrps, link) {
699
- rd_kafka_mock_cgrp_member_t *member, *tmp;
713
+ TAILQ_FOREACH(mcgrp, &mcluster->cgrps_classic, link) {
714
+ rd_kafka_mock_cgrp_classic_member_t *member, *tmp;
700
715
  TAILQ_FOREACH_SAFE(member, &mcgrp->members, link, tmp) {
701
716
  if (member->conn == mconn) {
702
717
  member->conn = NULL;
@@ -708,3 +723,1154 @@ void rd_kafka_mock_cgrps_connection_closed(rd_kafka_mock_cluster_t *mcluster,
708
723
  }
709
724
  }
710
725
  }
726
+
727
+ /**
728
+ * @struct Target assignment for a consumer group.
729
+ * `member_ids` and `assignment` are in the same order
730
+ * and have the same count.
731
+ */
732
+ typedef struct rd_kafka_mock_cgrp_consumer_target_assignment_s {
733
+ rd_list_t *member_ids; /**< Member id list (char *). */
734
+ rd_list_t *assignment; /**< Assingment list
735
+ (rd_kafka_topic_partition_list_t *). */
736
+ } rd_kafka_mock_cgrp_consumer_target_assignment_t;
737
+
738
+ static rd_kafka_mock_cgrp_consumer_target_assignment_t *
739
+ rd_kafka_mock_cgrp_consumer_target_assignment_new0(rd_list_t *member_ids,
740
+ rd_list_t *assignment) {
741
+ rd_assert(member_ids->rl_cnt == assignment->rl_cnt);
742
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment =
743
+ rd_calloc(1, sizeof(*target_assignment));
744
+ target_assignment->member_ids =
745
+ rd_list_copy(member_ids, rd_list_string_copy, NULL);
746
+ target_assignment->assignment = rd_list_copy(
747
+ assignment, rd_kafka_topic_partition_list_copy_opaque, NULL);
748
+ return target_assignment;
749
+ }
750
+
751
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *
752
+ rd_kafka_mock_cgrp_consumer_target_assignment_new(
753
+ char **member_ids,
754
+ int member_cnt,
755
+ rd_kafka_topic_partition_list_t **assignment) {
756
+ int i;
757
+ rd_list_t *member_id_list, *assignment_list;
758
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *ret;
759
+
760
+ member_id_list = rd_list_new(member_cnt, rd_free);
761
+ assignment_list =
762
+ rd_list_new(member_cnt, rd_kafka_topic_partition_list_destroy_free);
763
+ for (i = 0; i < member_cnt; i++) {
764
+ rd_list_add(member_id_list, rd_strdup(member_ids[i]));
765
+ rd_list_add(assignment_list,
766
+ rd_kafka_topic_partition_list_copy(assignment[i]));
767
+ }
768
+
769
+ ret = rd_kafka_mock_cgrp_consumer_target_assignment_new0(
770
+ member_id_list, assignment_list);
771
+ rd_list_destroy(member_id_list);
772
+ rd_list_destroy(assignment_list);
773
+ return ret;
774
+ }
775
+
776
+ void rd_kafka_mock_cgrp_consumer_target_assignment_destroy(
777
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment) {
778
+ rd_list_destroy(target_assignment->member_ids);
779
+ rd_list_destroy(target_assignment->assignment);
780
+ rd_free(target_assignment);
781
+ }
782
+
783
+ /**
784
+ * @brief Sets next target assignment and member epoch for \p member
785
+ * to a copy of partition list \p rktparlist,
786
+ * filling its topic ids if not provided, using \p cgrp cluster topics.
787
+ *
788
+ * @param mcgrp The consumer group containing the member.
789
+ * @param member A consumer group member.
790
+ * @param target_member_epoch New member epoch.
791
+ * @param rktparlist Next target assignment.
792
+ *
793
+ * @locks mcluster->lock MUST be held.
794
+ */
795
+ static void rd_kafka_mock_cgrp_consumer_member_target_assignment_set(
796
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
797
+ rd_kafka_mock_cgrp_consumer_member_t *member,
798
+ int target_member_epoch,
799
+ const rd_kafka_topic_partition_list_t *rktparlist) {
800
+ rd_kafka_topic_partition_t *rktpar;
801
+ if (member->target_assignment) {
802
+ rd_kafka_topic_partition_list_destroy(
803
+ member->target_assignment);
804
+ }
805
+ member->target_member_epoch = target_member_epoch;
806
+ member->target_assignment =
807
+ rd_kafka_topic_partition_list_copy(rktparlist);
808
+
809
+ /* If not present, fill topic ids using names */
810
+ RD_KAFKA_TPLIST_FOREACH(rktpar, member->target_assignment) {
811
+ rd_kafka_Uuid_t topic_id =
812
+ rd_kafka_topic_partition_get_topic_id(rktpar);
813
+ if (!rd_kafka_Uuid_cmp(topic_id, RD_KAFKA_UUID_ZERO)) {
814
+ rd_kafka_mock_topic_t *mtopic =
815
+ rd_kafka_mock_topic_find(mcgrp->cluster,
816
+ rktpar->topic);
817
+ if (mtopic)
818
+ rd_kafka_topic_partition_set_topic_id(
819
+ rktpar, mtopic->id);
820
+ }
821
+ }
822
+ }
823
+
824
+ /**
825
+ * @brief Sets next target assignment for group \p mcgrp
826
+ * to a copy of \p target_assignment partition lists.
827
+ *
828
+ * @param mcgrp The consumer group.
829
+ * @param target_assignment Target assignment for all members.
830
+ *
831
+ * @locks mcluster->lock MUST be held.
832
+ */
833
+ static void rd_kafka_mock_cgrp_consumer_target_assignment_set(
834
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
835
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment) {
836
+ int i = 0;
837
+ int32_t new_target_member_epoch;
838
+ const char *member_id;
839
+ rd_kafka_mock_cgrp_consumer_member_t *member;
840
+
841
+ mcgrp->group_epoch++;
842
+ new_target_member_epoch = mcgrp->group_epoch;
843
+ RD_LIST_FOREACH(member_id, target_assignment->member_ids, i) {
844
+ rd_kafkap_str_t *member_id_str =
845
+ rd_kafkap_str_new(member_id, strlen(member_id));
846
+ rd_kafka_topic_partition_list_t *member_assignment =
847
+ rd_list_elem(target_assignment->assignment, i);
848
+ member = rd_kafka_mock_cgrp_consumer_member_find(mcgrp,
849
+ member_id_str);
850
+ rd_kafkap_str_destroy(member_id_str);
851
+
852
+ if (!member)
853
+ continue;
854
+
855
+ rd_kafka_mock_cgrp_consumer_member_target_assignment_set(
856
+ mcgrp, member, new_target_member_epoch, member_assignment);
857
+ }
858
+ }
859
+
860
+ typedef RD_MAP_TYPE(const char *, rd_list_t *) map_str_list;
861
+ typedef RD_MAP_TYPE(const char *, int *) map_str_int;
862
+
863
+ /**
864
+ * @brief Calculate a simple range target assignment for the consumer group \p
865
+ * mcgrp. This isn't replicating any given broker assignor but is used
866
+ * when the test doesn't need a specific type of assignment.
867
+ *
868
+ * If the test needs it, instead of replicating same conditions with all the
869
+ * members, one can mock the assignment directly with
870
+ * `rd_kafka_mock_cgrp_consumer_target_assignment`.
871
+ */
872
+ static rd_kafka_mock_cgrp_consumer_target_assignment_t *
873
+ rd_kafka_mock_cgrp_consumer_target_assignment_calculate_range(
874
+ const rd_kafka_mock_cgrp_consumer_t *mcgrp) {
875
+ int i, *i_pointer;
876
+ const char *topic;
877
+ rd_list_t *members;
878
+ rd_kafka_mock_cgrp_consumer_member_t *member;
879
+ rd_kafka_mock_cluster_t *mcluster = mcgrp->cluster;
880
+ /* List of member ids (char *) */
881
+ rd_list_t *member_ids = rd_list_new(mcgrp->member_cnt, rd_free);
882
+ /* List of member assignment (rd_kafka_topic_partition_list_t *) */
883
+ rd_list_t *assignment = rd_list_new(
884
+ mcgrp->member_cnt, rd_kafka_topic_partition_list_destroy_free);
885
+ /* Map from topic name to list of members */
886
+ map_str_list topic_members =
887
+ RD_MAP_INITIALIZER(mcgrp->member_cnt, rd_map_str_cmp,
888
+ rd_map_str_hash, NULL, rd_list_destroy_free);
889
+ /* Map from member id to index in the members and assignment lists. */
890
+ map_str_int member_idx = RD_MAP_INITIALIZER(
891
+ mcgrp->member_cnt, rd_map_str_cmp, rd_map_str_hash, NULL, rd_free);
892
+
893
+ i = 0;
894
+
895
+ /* First create a map with topics associated to the list of members
896
+ * and save the member idx in the `member_idx` map. */
897
+ TAILQ_FOREACH(member, &mcgrp->members, link) {
898
+ int j;
899
+ rd_list_add(member_ids, rd_strdup(member->id));
900
+ rd_list_add(assignment, rd_kafka_topic_partition_list_new(0));
901
+
902
+ RD_LIST_FOREACH(topic, member->subscribed_topics, j) {
903
+ if (!RD_MAP_GET(&topic_members, topic)) {
904
+ members = rd_list_new(0, NULL);
905
+ RD_MAP_SET(&topic_members, topic, members);
906
+ } else
907
+ members = RD_MAP_GET(&topic_members, topic);
908
+ rd_list_add(members, member);
909
+ }
910
+ i_pointer = rd_calloc(1, sizeof(*i_pointer));
911
+ *i_pointer = i;
912
+ RD_MAP_SET(&member_idx, member->id, i_pointer);
913
+ i++;
914
+ }
915
+
916
+ /* For each topic to a range assignment and add the
917
+ * corresponding partitions to the assignment for that member.
918
+ * Finds the list index using the `member_idx` map. */
919
+ RD_MAP_FOREACH(topic, members, &topic_members) {
920
+ rd_kafka_Uuid_t topic_id;
921
+ rd_kafka_topic_partition_list_t *member_assignment;
922
+ int members_cnt = rd_list_cnt(members);
923
+ int common, one_more, assigned = 0;
924
+ rd_kafkap_str_t Topic = {.str = topic, .len = strlen(topic)};
925
+ rd_kafka_mock_topic_t *mock_topic =
926
+ rd_kafka_mock_topic_find_by_kstr(mcluster, &Topic);
927
+ if (!mock_topic)
928
+ continue;
929
+
930
+ topic_id = mock_topic->id;
931
+
932
+ /* Assign one partition more
933
+ * to the first mock_topic->partition_cnt % members_cnt
934
+ * members. */
935
+ common = mock_topic->partition_cnt / members_cnt;
936
+ one_more = mock_topic->partition_cnt % members_cnt;
937
+
938
+ RD_LIST_FOREACH(member, members, i) {
939
+ int j, num_partitions = common;
940
+ int idx = *RD_MAP_GET(&member_idx, member->id);
941
+ member_assignment = rd_list_elem(assignment, idx);
942
+ if (idx < one_more)
943
+ num_partitions++;
944
+ for (j = 0; j < num_partitions; j++) {
945
+ rd_kafka_topic_partition_t *rktpar =
946
+ rd_kafka_topic_partition_list_add(
947
+ member_assignment, topic, assigned + j);
948
+ rd_kafka_topic_partition_set_topic_id(rktpar,
949
+ topic_id);
950
+ }
951
+ assigned += num_partitions;
952
+ }
953
+ }
954
+
955
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *ret =
956
+ rd_kafka_mock_cgrp_consumer_target_assignment_new0(member_ids,
957
+ assignment);
958
+
959
+ RD_MAP_DESTROY(&topic_members);
960
+ RD_MAP_DESTROY(&member_idx);
961
+
962
+ rd_list_destroy(member_ids);
963
+ rd_list_destroy(assignment);
964
+
965
+ return ret;
966
+ }
967
+
968
+ /**
969
+ * @brief Recalculate and set a target assignment for \p mcgrp
970
+ * only if `mcgrp->manual_assignment` isn't set.
971
+ *
972
+ * @locks mcluster->lock MUST be held.
973
+ */
974
+ static void rd_kafka_mock_cgrp_consumer_target_assignment_recalculate(
975
+ rd_kafka_mock_cgrp_consumer_t *mcgrp) {
976
+ if (mcgrp->manual_assignment)
977
+ return;
978
+
979
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment =
980
+ rd_kafka_mock_cgrp_consumer_target_assignment_calculate_range(
981
+ mcgrp);
982
+ rd_kafka_mock_cgrp_consumer_target_assignment_set(mcgrp,
983
+ target_assignment);
984
+ rd_kafka_mock_cgrp_consumer_target_assignment_destroy(
985
+ target_assignment);
986
+ }
987
+
988
+ /**
989
+ * @brief Set manual target assignment \p target_assignment
990
+ * to the consumer group \p mcgrp .
991
+ *
992
+ * @param mcgrp Consumer group
993
+ * @param target_assignment Target assignment to set.
994
+ * Pass NULL to return to automatic assignment.
995
+ *
996
+ * @locks mcluster->lock MUST be held.
997
+ */
998
+ static void rd_kafka_mock_cgrp_consumer_target_assignment_set_manual(
999
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1000
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment) {
1001
+ if (!target_assignment) {
1002
+ mcgrp->manual_assignment = rd_false;
1003
+ rd_kafka_mock_cgrp_consumer_target_assignment_recalculate(
1004
+ mcgrp);
1005
+ return;
1006
+ }
1007
+
1008
+ mcgrp->manual_assignment = rd_true;
1009
+
1010
+ rd_kafka_mock_cgrp_consumer_target_assignment_set(mcgrp,
1011
+ target_assignment);
1012
+ }
1013
+
1014
+ /**
1015
+ * @brief Sets \p member current assignment to a copy of
1016
+ * \p current_assignment.
1017
+ *
1018
+ * @param member A consumer group member.
1019
+ * @param current_assignment Current assignment to set.
1020
+ *
1021
+ * @locks mcluster->lock MUST be held.
1022
+ */
1023
+ static void rd_kafka_mock_cgrp_consumer_member_current_assignment_set(
1024
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1025
+ const rd_kafka_topic_partition_list_t *current_assignment) {
1026
+ if (member->current_assignment) {
1027
+ rd_kafka_topic_partition_list_destroy(
1028
+ member->current_assignment);
1029
+ }
1030
+
1031
+ member->current_assignment =
1032
+ current_assignment
1033
+ ? rd_kafka_topic_partition_list_copy(current_assignment)
1034
+ : NULL;
1035
+ }
1036
+
1037
+ /**
1038
+ * @brief Sets \p member returned assignment to a
1039
+ * copy of \p returned_assignment.
1040
+ *
1041
+ * @param member A consumer group member.
1042
+ * @param returned_assignment Returned assignment to set.
1043
+ *
1044
+ * @locks mcluster->lock MUST be held.
1045
+ */
1046
+ static void rd_kafka_mock_cgrp_consumer_member_returned_assignment_set(
1047
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1048
+ const rd_kafka_topic_partition_list_t *returned_assignment) {
1049
+ if (member->returned_assignment) {
1050
+ rd_kafka_topic_partition_list_destroy(
1051
+ member->returned_assignment);
1052
+ }
1053
+ member->returned_assignment =
1054
+ returned_assignment
1055
+ ? rd_kafka_topic_partition_list_copy(returned_assignment)
1056
+ : NULL;
1057
+ }
1058
+
1059
+ /**
1060
+ * @brief Returns a copy of \p member target assignment containing only
1061
+ * partitions that can be assignment, whose topic id is non-zero.
1062
+ *
1063
+ * @param member The group member.
1064
+ *
1065
+ * @remark The returned pointer ownership is transferred to the caller.
1066
+ *
1067
+ * @locks mcluster->lock MUST be held.
1068
+ */
1069
+ static rd_kafka_topic_partition_list_t *
1070
+ rd_kafka_mock_cgrp_consumer_member_target_assignment_assignable(
1071
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1072
+ rd_kafka_topic_partition_list_t *assignment = member->target_assignment;
1073
+ rd_kafka_topic_partition_t *rktpar;
1074
+ rd_kafka_topic_partition_list_t *ret =
1075
+ rd_kafka_topic_partition_list_new(assignment->cnt);
1076
+
1077
+ RD_KAFKA_TPLIST_FOREACH(rktpar, assignment) {
1078
+ rd_kafka_Uuid_t topic_id =
1079
+ rd_kafka_topic_partition_get_topic_id(rktpar);
1080
+ if (rd_kafka_Uuid_cmp(topic_id, RD_KAFKA_UUID_ZERO)) {
1081
+ rd_kafka_topic_partition_list_add_copy(ret, rktpar);
1082
+ }
1083
+ }
1084
+
1085
+ return ret;
1086
+ }
1087
+
1088
+ /**
1089
+ * Returns true iff \p new_assignment doesn't have any intersection with any
1090
+ * other member current assignment.
1091
+ *
1092
+ * If there's an intersection, it means we cannot bump the epoch at the moment,
1093
+ * because some of these partitions are held by a different member. They have
1094
+ * to be revoked from that member before it's possible to increase the epoch
1095
+ * and assign additional partitions to this member.
1096
+ */
1097
+ rd_bool_t rd_kafka_mock_cgrp_consumer_member_next_assignment_can_bump_epoch(
1098
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1099
+ rd_kafka_topic_partition_list_t *new_assignment) {
1100
+ rd_kafka_topic_partition_list_t *double_assignment,
1101
+ *assigned_partitions = rd_kafka_topic_partition_list_new(0);
1102
+ rd_kafka_mock_cgrp_consumer_member_t *other_member;
1103
+ rd_kafka_mock_cgrp_consumer_t *mcgrp = member->mcgrp;
1104
+ rd_bool_t ret;
1105
+
1106
+ TAILQ_FOREACH(other_member, &mcgrp->members, link) {
1107
+ int other_current_assignment_cnt = 0,
1108
+ other_returned_assignment_cnt = 0;
1109
+ if (member == other_member)
1110
+ continue;
1111
+ if (other_member->current_assignment)
1112
+ other_current_assignment_cnt =
1113
+ other_member->current_assignment->cnt;
1114
+ if (other_member->returned_assignment)
1115
+ other_returned_assignment_cnt =
1116
+ other_member->returned_assignment->cnt;
1117
+
1118
+ if (other_current_assignment_cnt > 0 &&
1119
+ other_current_assignment_cnt >
1120
+ other_returned_assignment_cnt) {
1121
+ /* This is the case where we're revoking
1122
+ * some partitions.
1123
+ * returned_assignment < current_assignment. */
1124
+ rd_kafka_topic_partition_list_add_list(
1125
+ assigned_partitions,
1126
+ other_member->current_assignment);
1127
+ } else if (other_returned_assignment_cnt > 0) {
1128
+ /* This is the case where we're assigning
1129
+ * some partitions.
1130
+ * returned_assignment >= current_assignment. */
1131
+ rd_kafka_topic_partition_list_add_list(
1132
+ assigned_partitions,
1133
+ other_member->returned_assignment);
1134
+ }
1135
+ }
1136
+ double_assignment = rd_kafka_topic_partition_list_intersection_by_id(
1137
+ new_assignment, assigned_partitions);
1138
+ ret = double_assignment->cnt == 0;
1139
+
1140
+ rd_kafka_topic_partition_list_destroy(assigned_partitions);
1141
+ rd_kafka_topic_partition_list_destroy(double_assignment);
1142
+ return ret;
1143
+ }
1144
+
1145
+ /**
1146
+ * @brief Calculates if \p member,
1147
+ * needs a revocation, that is if its current assignment
1148
+ * isn't a subset of its target assignment.
1149
+ * In case it needs a revocation, it returns
1150
+ * the intersection between the two assignments,
1151
+ * that is the remaining partitions after revocation
1152
+ * of those not included in target assignment.
1153
+ *
1154
+ * @param member The group member.
1155
+ *
1156
+ * @return The remaining set of partitions, or NULL in case no revocation
1157
+ * is needed.
1158
+ *
1159
+ * @remark The returned pointer ownership is transferred to the caller.
1160
+ *
1161
+ * @locks mcluster->lock MUST be held.
1162
+ */
1163
+ static rd_kafka_topic_partition_list_t *
1164
+ rd_kafka_mock_cgrp_consumer_member_needs_revocation(
1165
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1166
+ rd_kafka_topic_partition_list_t *intersection;
1167
+ rd_bool_t needs_revocation;
1168
+
1169
+ if (member->current_assignment)
1170
+ /* If we have a current assignment we
1171
+ * calculate the intersection with
1172
+ * target assignment. */
1173
+ intersection = rd_kafka_topic_partition_list_intersection_by_id(
1174
+ member->current_assignment, member->target_assignment);
1175
+ else
1176
+ /* Otherwise intersection is empty. */
1177
+ intersection = rd_kafka_topic_partition_list_new(0);
1178
+
1179
+ needs_revocation = member->current_assignment &&
1180
+ intersection->cnt < member->current_assignment->cnt;
1181
+ if (needs_revocation) {
1182
+ return intersection;
1183
+ }
1184
+
1185
+ rd_kafka_topic_partition_list_destroy(intersection);
1186
+ return NULL;
1187
+ }
1188
+
1189
+ /**
1190
+ * @brief Calculates if \p member,
1191
+ * can receive new partitions, given revocation is completed.
1192
+ * In case new partitions aren't held by other members it
1193
+ * returns the assignable target assignment and bumps current
1194
+ * member epoch, otherwise it returns NULL and
1195
+ * doesn't change current member epoch.
1196
+ *
1197
+ * @param member The group member.
1198
+ *
1199
+ * @return The assignable set of partitions, or NULL in case new partitions
1200
+ * cannot be assigned yet.
1201
+ *
1202
+ * @remark The returned pointer ownership is transferred to the caller.
1203
+ *
1204
+ * @locks mcluster->lock MUST be held.
1205
+ */
1206
+ static rd_kafka_topic_partition_list_t *
1207
+ rd_kafka_mock_cgrp_consumer_member_needs_assignment(
1208
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1209
+ rd_kafka_topic_partition_list_t *returned_assignment =
1210
+ rd_kafka_mock_cgrp_consumer_member_target_assignment_assignable(
1211
+ member);
1212
+
1213
+ if (!rd_kafka_mock_cgrp_consumer_member_next_assignment_can_bump_epoch(
1214
+ member, returned_assignment)) {
1215
+ /* We can't bump the epoch still,
1216
+ * there are some partitions held by other members.
1217
+ * We have to return NULL. */
1218
+ rd_kafka_topic_partition_list_destroy(returned_assignment);
1219
+ return NULL;
1220
+ }
1221
+
1222
+ /* No partitions to remove, return
1223
+ * target assignment and reconcile the
1224
+ * epochs */
1225
+ member->current_member_epoch = member->target_member_epoch;
1226
+ return returned_assignment;
1227
+ }
1228
+
1229
+ /**
1230
+ * @brief Calculates next assignment and member epoch for a \p member,
1231
+ * given \p current_assignment.
1232
+ *
1233
+ * @param member The group member.
1234
+ * @param current_assignment The assignment sent by the member, or NULL if it
1235
+ * didn't change. Must be NULL if *member_epoch is 0.
1236
+ * @param member_epoch Pointer to client reported member epoch. Can be updated.
1237
+ *
1238
+ * @return The new assignment to return to the member.
1239
+ *
1240
+ * @remark The returned pointer ownership is transferred to the caller.
1241
+ *
1242
+ * @locks mcluster->lock MUST be held.
1243
+ */
1244
+ rd_kafka_topic_partition_list_t *
1245
+ rd_kafka_mock_cgrp_consumer_member_next_assignment(
1246
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1247
+ rd_kafka_topic_partition_list_t *current_assignment,
1248
+ int *member_epoch) {
1249
+ rd_kafka_topic_partition_list_t *assignment_to_return = NULL;
1250
+
1251
+ if (current_assignment) {
1252
+ /* Update current assignment to reflect what is provided
1253
+ * by the client. */
1254
+ rd_kafka_mock_cgrp_consumer_member_current_assignment_set(
1255
+ member, current_assignment);
1256
+ }
1257
+
1258
+ if (*member_epoch > 0 &&
1259
+ member->current_member_epoch != *member_epoch) {
1260
+ /* Member epoch is different from the one we expect,
1261
+ * that means we have to fence the member. */
1262
+ *member_epoch = -1; /* FENCED_MEMBER_EPOCH */
1263
+ return NULL;
1264
+ }
1265
+
1266
+ if (member->target_assignment) {
1267
+ /* We have a target assignment,
1268
+ * let's check if we can assign it. */
1269
+
1270
+ if (*member_epoch != member->current_member_epoch ||
1271
+ member->current_member_epoch !=
1272
+ member->target_member_epoch) {
1273
+ /* Epochs are different, that means we have to bump the
1274
+ * epoch immediately or do some revocations
1275
+ * before that. */
1276
+
1277
+ assignment_to_return =
1278
+ rd_kafka_mock_cgrp_consumer_member_needs_revocation(
1279
+ member);
1280
+ if (!assignment_to_return) {
1281
+ /* After revocation we only have to
1282
+ * add new partitions.
1283
+ * In case these new partitions are held
1284
+ * by other members we still cannot do it. */
1285
+ assignment_to_return =
1286
+ rd_kafka_mock_cgrp_consumer_member_needs_assignment(
1287
+ member);
1288
+ }
1289
+ } else if (!member->returned_assignment) {
1290
+ /* If all the epochs are the same, the only case
1291
+ * where we have to return the assignment is
1292
+ * after a disconnection, when returned_assignment has
1293
+ * been reset to NULL. */
1294
+ assignment_to_return =
1295
+ rd_kafka_mock_cgrp_consumer_member_target_assignment_assignable(
1296
+ member);
1297
+ }
1298
+ }
1299
+
1300
+ *member_epoch = member->current_member_epoch;
1301
+ if (assignment_to_return) {
1302
+ /* Compare assignment_to_return with last returned_assignment.
1303
+ * If equal, return NULL, otherwise return assignment_to_return
1304
+ * and update last returned_assignment. */
1305
+ rd_bool_t same_returned_assignment =
1306
+ member->returned_assignment &&
1307
+ !rd_kafka_topic_partition_list_cmp(
1308
+ member->returned_assignment, assignment_to_return,
1309
+ rd_kafka_topic_partition_by_id_cmp);
1310
+
1311
+ if (same_returned_assignment) {
1312
+ /* Returned assignment is the same as previous
1313
+ * one, we return NULL instead to show no change. */
1314
+ rd_kafka_topic_partition_list_destroy(
1315
+ assignment_to_return);
1316
+ assignment_to_return = NULL;
1317
+ } else {
1318
+ /* We store returned assignment
1319
+ * for later comparison. */
1320
+ rd_kafka_mock_cgrp_consumer_member_returned_assignment_set(
1321
+ member, assignment_to_return);
1322
+ }
1323
+ }
1324
+ return assignment_to_return;
1325
+ }
1326
+
1327
+ /**
1328
+ * @brief Mark member as active (restart session timer).
1329
+ *
1330
+ * @param mcgrp Member's consumer group.
1331
+ * @param member Member to set as active.
1332
+ *
1333
+ * @locks mcluster->lock MUST be held.
1334
+ */
1335
+ void rd_kafka_mock_cgrp_consumer_member_active(
1336
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1337
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1338
+ rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
1339
+ "Marking mock consumer group member %s as active",
1340
+ member->id);
1341
+ member->ts_last_activity = rd_clock();
1342
+ }
1343
+
1344
+ /**
1345
+ * @brief Finds a member in consumer group \p mcgrp by \p MemberId.
1346
+ *
1347
+ * @param mcgrp Consumer group to search.
1348
+ * @param MemberId Member id to look for.
1349
+ * @return Found member or NULL.
1350
+ *
1351
+ * @locks mcluster->lock MUST be held.
1352
+ */
1353
+ rd_kafka_mock_cgrp_consumer_member_t *rd_kafka_mock_cgrp_consumer_member_find(
1354
+ const rd_kafka_mock_cgrp_consumer_t *mcgrp,
1355
+ const rd_kafkap_str_t *MemberId) {
1356
+ const rd_kafka_mock_cgrp_consumer_member_t *member;
1357
+ TAILQ_FOREACH(member, &mcgrp->members, link) {
1358
+ if (!rd_kafkap_str_cmp_str(MemberId, member->id))
1359
+ return (rd_kafka_mock_cgrp_consumer_member_t *)member;
1360
+ }
1361
+
1362
+ return NULL;
1363
+ }
1364
+
1365
+ /**
1366
+ * @brief Finds a member in consumer group \p mcgrp by \p InstanceId.
1367
+ *
1368
+ * @param mcgrp Consumer group to search.
1369
+ * @param InstanceId Instance id to look for.
1370
+ * @return Found member or NULL.
1371
+ *
1372
+ * @locks mcluster->lock MUST be held.
1373
+ */
1374
+ rd_kafka_mock_cgrp_consumer_member_t *
1375
+ rd_kafka_mock_cgrp_consumer_member_find_by_instance_id(
1376
+ const rd_kafka_mock_cgrp_consumer_t *mcgrp,
1377
+ const rd_kafkap_str_t *InstanceId) {
1378
+ if (RD_KAFKAP_STR_IS_NULL(InstanceId))
1379
+ return NULL;
1380
+
1381
+ const rd_kafka_mock_cgrp_consumer_member_t *member;
1382
+ TAILQ_FOREACH(member, &mcgrp->members, link) {
1383
+ if (!member->instance_id)
1384
+ continue;
1385
+
1386
+ if (!rd_kafkap_str_cmp_str(InstanceId, member->instance_id))
1387
+ return (rd_kafka_mock_cgrp_consumer_member_t *)member;
1388
+ }
1389
+
1390
+ return NULL;
1391
+ }
1392
+
1393
+ static void validate_subscription(const rd_kafkap_str_t *SubscribedTopicNames,
1394
+ int32_t SubscribedTopicNamesCnt,
1395
+ const rd_kafkap_str_t *SubscribedTopicRegex) {
1396
+ /* Either they are both NULL
1397
+ * or both non-NULL. */
1398
+ rd_assert((SubscribedTopicNames == NULL) ==
1399
+ RD_KAFKAP_STR_IS_NULL(SubscribedTopicRegex));
1400
+ /* If they're not NULL at least one should be non-empty */
1401
+ rd_assert(SubscribedTopicNames == NULL || SubscribedTopicNamesCnt > 0 ||
1402
+ RD_KAFKAP_STR_LEN(SubscribedTopicRegex) > 0);
1403
+ }
1404
+
1405
+ /**
1406
+ * @brief Set the subscribed topics for the member \p member based on \p
1407
+ * SubscribedTopicNames and \p SubscribedTopicRegex. Deduplicates the list after
1408
+ * sorting it.
1409
+ * @return `rd_true` if the subscription was changed, that happens
1410
+ * if it's set and different from previous one.
1411
+ *
1412
+ * @locks mcluster->lock MUST be held.
1413
+ */
1414
+ static rd_bool_t rd_kafka_mock_cgrp_consumer_member_subscribed_topic_names_set(
1415
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1416
+ rd_kafkap_str_t *SubscribedTopicNames,
1417
+ int32_t SubscribedTopicNamesCnt,
1418
+ const rd_kafkap_str_t *SubscribedTopicRegex) {
1419
+ rd_bool_t changed = rd_false;
1420
+ rd_list_t *new_subscription;
1421
+ int32_t i;
1422
+
1423
+ validate_subscription(SubscribedTopicNames, SubscribedTopicNamesCnt,
1424
+ SubscribedTopicRegex);
1425
+
1426
+ if (!SubscribedTopicNames &&
1427
+ RD_KAFKAP_STR_IS_NULL(SubscribedTopicRegex) &&
1428
+ !member->subscribed_topic_regex) {
1429
+ /* When client is sending NULL for SubscribedTopicNames and
1430
+ * SubscribedTopicRegex, its subscription didn't change. If we
1431
+ * already had a regex, we need to compute the regex again. */
1432
+ return changed;
1433
+ }
1434
+
1435
+ if (SubscribedTopicNames) {
1436
+ RD_IF_FREE(member->subscribed_topic_names, rd_list_destroy);
1437
+ member->subscribed_topic_names =
1438
+ rd_list_new(SubscribedTopicNamesCnt, rd_free);
1439
+ for (i = 0; i < SubscribedTopicNamesCnt; i++) {
1440
+ rd_list_add(
1441
+ member->subscribed_topic_names,
1442
+ RD_KAFKAP_STR_DUP(&SubscribedTopicNames[i]));
1443
+ }
1444
+ }
1445
+
1446
+ if (!RD_KAFKAP_STR_IS_NULL(SubscribedTopicRegex)) {
1447
+ RD_IF_FREE(member->subscribed_topic_regex, rd_free);
1448
+ member->subscribed_topic_regex =
1449
+ RD_KAFKAP_STR_DUP(SubscribedTopicRegex);
1450
+ }
1451
+
1452
+ new_subscription =
1453
+ rd_list_new(rd_list_cnt(member->subscribed_topic_names), rd_free);
1454
+
1455
+ rd_list_copy_to(new_subscription, member->subscribed_topic_names,
1456
+ rd_list_string_copy, NULL);
1457
+
1458
+ if (member->subscribed_topic_regex[0]) {
1459
+ rd_kafka_mock_cluster_t *mcluster = member->mcgrp->cluster;
1460
+ rd_kafka_mock_topic_t *mtopic;
1461
+ char errstr[1];
1462
+ rd_regex_t *re = rd_regex_comp(member->subscribed_topic_regex,
1463
+ errstr, sizeof(errstr));
1464
+
1465
+ TAILQ_FOREACH(mtopic, &mcluster->topics, link) {
1466
+ if (rd_regex_exec(re, mtopic->name))
1467
+ rd_list_add(new_subscription,
1468
+ rd_strdup(mtopic->name));
1469
+ }
1470
+
1471
+ rd_regex_destroy(re);
1472
+ }
1473
+
1474
+ rd_list_deduplicate(&new_subscription, rd_strcmp2);
1475
+
1476
+ if (!member->subscribed_topics ||
1477
+ rd_list_cmp(new_subscription, member->subscribed_topics,
1478
+ rd_list_cmp_str)) {
1479
+ if (member->subscribed_topics)
1480
+ rd_list_destroy(member->subscribed_topics);
1481
+ member->subscribed_topics =
1482
+ rd_list_copy(new_subscription, rd_list_string_copy, NULL);
1483
+ changed = rd_true;
1484
+ }
1485
+ rd_list_destroy(new_subscription);
1486
+ return changed;
1487
+ }
1488
+
1489
+ static void rd_kafka_mock_cgrp_consumer_member_topic_id_set(
1490
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1491
+ const rd_kafkap_str_t *MemberId) {
1492
+ /* KIP 1082: MemberId is generated by the client */
1493
+ rd_assert(RD_KAFKAP_STR_LEN(MemberId) > 0);
1494
+ RD_IF_FREE(member->id, rd_free);
1495
+ member->id = RD_KAFKAP_STR_DUP(MemberId);
1496
+ }
1497
+
1498
+ /**
1499
+ * @brief Adds a member to consumer group \p mcgrp. If member with same
1500
+ * \p MemberId is already present, only updates the connection and
1501
+ * sets it as active.
1502
+ *
1503
+ * @param mcgrp Consumer group to add the member to.
1504
+ * @param conn Member connection.
1505
+ * @param MemberId Member id.
1506
+ * @param InstanceId Group instance id (optional).
1507
+ * @param session_timeout_ms Session timeout to use.
1508
+ * @param SubscribedTopicNames Array of subscribed topics.
1509
+ * Mandatory if the member is a new one.
1510
+ * @param SubscribedTopicNamesCnt Number of elements in \p SubscribedTopicNames.
1511
+ * @param SubscribedTopicRegex Subscribed topic regex.
1512
+ *
1513
+ * @return New or existing member, NULL if the member cannot be added.
1514
+ *
1515
+ * @locks mcluster->lock MUST be held.
1516
+ */
1517
+ rd_kafka_mock_cgrp_consumer_member_t *rd_kafka_mock_cgrp_consumer_member_add(
1518
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1519
+ struct rd_kafka_mock_connection_s *conn,
1520
+ const rd_kafkap_str_t *MemberId,
1521
+ const rd_kafkap_str_t *InstanceId,
1522
+ rd_kafkap_str_t *SubscribedTopicNames,
1523
+ int32_t SubscribedTopicNamesCnt,
1524
+ const rd_kafkap_str_t *SubscribedTopicRegex) {
1525
+ rd_kafka_mock_cgrp_consumer_member_t *member = NULL;
1526
+ rd_bool_t changed = rd_false;
1527
+
1528
+ /* Find member */
1529
+ member = rd_kafka_mock_cgrp_consumer_member_find(mcgrp, MemberId);
1530
+ if (!member) {
1531
+ member = rd_kafka_mock_cgrp_consumer_member_find_by_instance_id(
1532
+ mcgrp, InstanceId);
1533
+
1534
+ if (member) {
1535
+ if (!member->left_static_membership) {
1536
+ /* Old member still active,
1537
+ * fence this one */
1538
+ return NULL;
1539
+ }
1540
+
1541
+ if (rd_kafkap_str_cmp_str(MemberId, member->id) != 0) {
1542
+ /* Member is a new instance and is rejoining
1543
+ * with a new MemberId. */
1544
+ rd_kafka_mock_cgrp_consumer_member_topic_id_set(
1545
+ member, MemberId);
1546
+ }
1547
+ member->left_static_membership = rd_false;
1548
+ }
1549
+ } else {
1550
+ member->left_static_membership = rd_false;
1551
+ }
1552
+
1553
+ if (!member) {
1554
+ validate_subscription(SubscribedTopicNames,
1555
+ SubscribedTopicNamesCnt,
1556
+ SubscribedTopicRegex);
1557
+
1558
+ /* In case of session timeout
1559
+ * where the member isn't aware it's been fenced. */
1560
+ if (SubscribedTopicNames == NULL)
1561
+ return NULL;
1562
+
1563
+ /* Not found, add member */
1564
+ member = rd_calloc(1, sizeof(*member));
1565
+ member->mcgrp = mcgrp;
1566
+
1567
+ rd_kafka_mock_cgrp_consumer_member_topic_id_set(member,
1568
+ MemberId);
1569
+
1570
+ if (!RD_KAFKAP_STR_IS_NULL(InstanceId))
1571
+ member->instance_id = RD_KAFKAP_STR_DUP(InstanceId);
1572
+
1573
+ TAILQ_INSERT_TAIL(&mcgrp->members, member, link);
1574
+ mcgrp->member_cnt++;
1575
+ changed = rd_true;
1576
+ member->target_member_epoch = mcgrp->group_epoch;
1577
+ }
1578
+
1579
+ changed |=
1580
+ rd_kafka_mock_cgrp_consumer_member_subscribed_topic_names_set(
1581
+ member, SubscribedTopicNames, SubscribedTopicNamesCnt,
1582
+ SubscribedTopicRegex);
1583
+
1584
+ mcgrp->session_timeout_ms =
1585
+ mcgrp->cluster->defaults.group_consumer_session_timeout_ms;
1586
+ mcgrp->heartbeat_interval_ms =
1587
+ mcgrp->cluster->defaults.group_consumer_heartbeat_interval_ms;
1588
+
1589
+ member->conn = conn;
1590
+
1591
+ rd_kafka_mock_cgrp_consumer_member_active(mcgrp, member);
1592
+
1593
+ if (changed)
1594
+ rd_kafka_mock_cgrp_consumer_target_assignment_recalculate(
1595
+ mcgrp);
1596
+
1597
+ return member;
1598
+ }
1599
+
1600
+ /**
1601
+ * @brief Destroys a consumer group member, removing from its consumer group.
1602
+ *
1603
+ * @param mcgrp Member consumer group.
1604
+ * @param member Member to destroy.
1605
+ *
1606
+ * @locks mcluster->lock MUST be held.
1607
+ */
1608
+ static void rd_kafka_mock_cgrp_consumer_member_destroy(
1609
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1610
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1611
+ rd_assert(mcgrp->member_cnt > 0);
1612
+ TAILQ_REMOVE(&mcgrp->members, member, link);
1613
+ mcgrp->member_cnt--;
1614
+
1615
+ rd_kafka_mock_cgrp_consumer_target_assignment_recalculate(mcgrp);
1616
+
1617
+ rd_free(member->id);
1618
+
1619
+ if (member->instance_id)
1620
+ rd_free(member->instance_id);
1621
+
1622
+ RD_IF_FREE(member->target_assignment,
1623
+ rd_kafka_topic_partition_list_destroy);
1624
+ RD_IF_FREE(member->current_assignment,
1625
+ rd_kafka_topic_partition_list_destroy);
1626
+ RD_IF_FREE(member->returned_assignment,
1627
+ rd_kafka_topic_partition_list_destroy);
1628
+ RD_IF_FREE(member->subscribed_topics, rd_list_destroy_free);
1629
+
1630
+ RD_IF_FREE(member->subscribed_topic_names, rd_list_destroy_free);
1631
+
1632
+ RD_IF_FREE(member->subscribed_topic_regex, rd_free);
1633
+
1634
+ rd_free(member);
1635
+ }
1636
+
1637
+ static void rd_kafka_mock_cgrp_consumer_member_leave_static(
1638
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1639
+ member->left_static_membership = rd_true;
1640
+ rd_kafka_mock_cgrp_consumer_member_returned_assignment_set(member,
1641
+ NULL);
1642
+ }
1643
+
1644
+
1645
+ /**
1646
+ * @brief Called when a member must leave a consumer group.
1647
+ *
1648
+ * @param mcgrp Consumer group to leave.
1649
+ * @param member Member that leaves.
1650
+ * @param leave_static If true, the member is leaving with static group
1651
+ * membership.
1652
+ *
1653
+ * @locks mcluster->lock MUST be held.
1654
+ */
1655
+ void rd_kafka_mock_cgrp_consumer_member_leave(
1656
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1657
+ rd_kafka_mock_cgrp_consumer_member_t *member,
1658
+ rd_bool_t leave_static) {
1659
+ rd_bool_t is_static = member->instance_id != NULL;
1660
+
1661
+ rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
1662
+ "Member %s is leaving group %s, is static: %s, "
1663
+ "static leave: %s",
1664
+ member->id, mcgrp->id, RD_STR_ToF(is_static),
1665
+ RD_STR_ToF(leave_static));
1666
+ if (!is_static || !leave_static)
1667
+ rd_kafka_mock_cgrp_consumer_member_destroy(mcgrp, member);
1668
+ else
1669
+ rd_kafka_mock_cgrp_consumer_member_leave_static(member);
1670
+ }
1671
+
1672
+ /**
1673
+ * @brief Called when a member is fenced from a consumer group.
1674
+ *
1675
+ * @param mcgrp Consumer group.
1676
+ * @param member Member to fence.
1677
+ *
1678
+ * @locks mcluster->lock MUST be held.
1679
+ */
1680
+ void rd_kafka_mock_cgrp_consumer_member_fenced(
1681
+ rd_kafka_mock_cgrp_consumer_t *mcgrp,
1682
+ rd_kafka_mock_cgrp_consumer_member_t *member) {
1683
+
1684
+ rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
1685
+ "Member %s is fenced from group %s", member->id,
1686
+ mcgrp->id);
1687
+
1688
+ rd_kafka_mock_cgrp_consumer_member_destroy(mcgrp, member);
1689
+ }
1690
+
1691
+ /**
1692
+ * @brief Find a consumer group in cluster \p mcluster by \p GroupId.
1693
+ *
1694
+ * @param mcluster Cluster to search in.
1695
+ * @param GroupId Group id to search.
1696
+ * @return Found group or NULL.
1697
+ *
1698
+ * @locks mcluster->lock MUST be held.
1699
+ */
1700
+ rd_kafka_mock_cgrp_consumer_t *
1701
+ rd_kafka_mock_cgrp_consumer_find(const rd_kafka_mock_cluster_t *mcluster,
1702
+ const rd_kafkap_str_t *GroupId) {
1703
+ rd_kafka_mock_cgrp_consumer_t *mcgrp;
1704
+ TAILQ_FOREACH(mcgrp, &mcluster->cgrps_consumer, link) {
1705
+ if (!rd_kafkap_str_cmp_str(GroupId, mcgrp->id))
1706
+ return mcgrp;
1707
+ }
1708
+
1709
+ return NULL;
1710
+ }
1711
+
1712
+ /**
1713
+ * @brief Check if any members have exceeded the session timeout.
1714
+ *
1715
+ * @param rkts Timers.
1716
+ * @param arg Consumer group.
1717
+ *
1718
+ * @locks mcluster->lock is acquired and released.
1719
+ */
1720
+ static void rd_kafka_mock_cgrp_consumer_session_tmr_cb(rd_kafka_timers_t *rkts,
1721
+ void *arg) {
1722
+ rd_kafka_mock_cgrp_consumer_t *mcgrp = arg;
1723
+ rd_kafka_mock_cgrp_consumer_member_t *member, *tmp;
1724
+ rd_ts_t now = rd_clock();
1725
+ rd_kafka_mock_cluster_t *mcluster = mcgrp->cluster;
1726
+
1727
+ mtx_unlock(&mcluster->lock);
1728
+ TAILQ_FOREACH_SAFE(member, &mcgrp->members, link, tmp) {
1729
+ if (member->ts_last_activity +
1730
+ (mcgrp->session_timeout_ms * 1000) >
1731
+ now)
1732
+ continue;
1733
+
1734
+ rd_kafka_dbg(mcgrp->cluster->rk, MOCK, "MOCK",
1735
+ "Member %s session timed out for group %s",
1736
+ member->id, mcgrp->id);
1737
+
1738
+ rd_kafka_mock_cgrp_consumer_member_fenced(mcgrp, member);
1739
+ }
1740
+ mtx_unlock(&mcluster->lock);
1741
+ }
1742
+
1743
+
1744
+ /**
1745
+ * @brief Find or create a "consumer" consumer group.
1746
+ *
1747
+ * @param mcluster Cluster to search in.
1748
+ * @param GroupId Group id to look for.
1749
+ * @return Found or new consumer group.
1750
+ *
1751
+ * @locks mcluster->lock MUST be held.
1752
+ */
1753
+ rd_kafka_mock_cgrp_consumer_t *
1754
+ rd_kafka_mock_cgrp_consumer_get(rd_kafka_mock_cluster_t *mcluster,
1755
+ const rd_kafkap_str_t *GroupId) {
1756
+ rd_kafka_mock_cgrp_consumer_t *mcgrp;
1757
+
1758
+ mcgrp = rd_kafka_mock_cgrp_consumer_find(mcluster, GroupId);
1759
+ if (mcgrp)
1760
+ return mcgrp;
1761
+
1762
+ mcgrp = rd_calloc(1, sizeof(*mcgrp));
1763
+ mcgrp->cluster = mcluster;
1764
+ mcgrp->id = RD_KAFKAP_STR_DUP(GroupId);
1765
+ mcgrp->group_epoch = 1;
1766
+ TAILQ_INIT(&mcgrp->members);
1767
+ rd_kafka_timer_start(&mcluster->timers, &mcgrp->session_tmr,
1768
+ 1000 * 1000 /*1s*/,
1769
+ rd_kafka_mock_cgrp_consumer_session_tmr_cb, mcgrp);
1770
+
1771
+ TAILQ_INSERT_TAIL(&mcluster->cgrps_consumer, mcgrp, link);
1772
+
1773
+ return mcgrp;
1774
+ }
1775
+
1776
+
1777
+ void rd_kafka_mock_cgrp_consumer_target_assignment(
1778
+ rd_kafka_mock_cluster_t *mcluster,
1779
+ const char *group_id,
1780
+ rd_kafka_mock_cgrp_consumer_target_assignment_t *target_assignment) {
1781
+ rd_kafka_mock_cgrp_consumer_t *mcgrp;
1782
+ rd_kafkap_str_t *group_id_str =
1783
+ rd_kafkap_str_new(group_id, strlen(group_id));
1784
+
1785
+ mtx_lock(&mcluster->lock);
1786
+
1787
+ mcgrp = rd_kafka_mock_cgrp_consumer_find(mcluster, group_id_str);
1788
+ if (!mcgrp)
1789
+ goto destroy;
1790
+
1791
+ rd_kafka_mock_cgrp_consumer_target_assignment_set_manual(
1792
+ mcgrp, target_assignment);
1793
+
1794
+ destroy:
1795
+ rd_kafkap_str_destroy(group_id_str);
1796
+ mtx_unlock(&mcluster->lock);
1797
+ }
1798
+
1799
+ void rd_kafka_mock_set_group_consumer_session_timeout_ms(
1800
+ rd_kafka_mock_cluster_t *mcluster,
1801
+ int group_consumer_session_timeout_ms) {
1802
+ mtx_lock(&mcluster->lock);
1803
+ mcluster->defaults.group_consumer_session_timeout_ms =
1804
+ group_consumer_session_timeout_ms;
1805
+ mtx_unlock(&mcluster->lock);
1806
+ }
1807
+
1808
+ void rd_kafka_mock_set_group_consumer_heartbeat_interval_ms(
1809
+ rd_kafka_mock_cluster_t *mcluster,
1810
+ int group_consumer_heartbeat_interval_ms) {
1811
+ mtx_lock(&mcluster->lock);
1812
+ mcluster->defaults.group_consumer_heartbeat_interval_ms =
1813
+ group_consumer_heartbeat_interval_ms;
1814
+ mtx_unlock(&mcluster->lock);
1815
+ }
1816
+
1817
+ /**
1818
+ * @brief A client connection closed, check if any consumer cgrp has any state
1819
+ * for this connection that needs to be cleared.
1820
+ *
1821
+ * @param mcluster Cluster to search in.
1822
+ * @param mconn Connection that was closed.
1823
+ *
1824
+ * @locks mcluster->lock MUST be held.
1825
+ */
1826
+ void rd_kafka_mock_cgrps_consumer_connection_closed(
1827
+ rd_kafka_mock_cluster_t *mcluster,
1828
+ rd_kafka_mock_connection_t *mconn) {
1829
+ rd_kafka_mock_cgrp_consumer_t *mcgrp;
1830
+
1831
+ TAILQ_FOREACH(mcgrp, &mcluster->cgrps_consumer, link) {
1832
+ rd_kafka_mock_cgrp_consumer_member_t *member, *tmp;
1833
+ TAILQ_FOREACH_SAFE(member, &mcgrp->members, link, tmp) {
1834
+ if (member->conn == mconn) {
1835
+ member->conn = NULL;
1836
+ rd_kafka_mock_cgrp_consumer_member_returned_assignment_set(
1837
+ member, NULL);
1838
+ rd_kafka_mock_cgrp_consumer_member_current_assignment_set(
1839
+ member, NULL);
1840
+ }
1841
+ }
1842
+ }
1843
+ }
1844
+
1845
+ /**
1846
+ * @brief Destroys consumer group \p mcgrp and all of its members.
1847
+ *
1848
+ * @param mcgrp Consumer group to destroy.
1849
+ *
1850
+ * @locks mcluster->lock MUST be held.
1851
+ */
1852
+ void rd_kafka_mock_cgrp_consumer_destroy(rd_kafka_mock_cgrp_consumer_t *mcgrp) {
1853
+ rd_kafka_mock_cgrp_consumer_member_t *member;
1854
+
1855
+ TAILQ_REMOVE(&mcgrp->cluster->cgrps_consumer, mcgrp, link);
1856
+
1857
+ rd_kafka_timer_stop(&mcgrp->cluster->timers, &mcgrp->session_tmr,
1858
+ rd_true);
1859
+ rd_free(mcgrp->id);
1860
+ while ((member = TAILQ_FIRST(&mcgrp->members)))
1861
+ rd_kafka_mock_cgrp_consumer_member_destroy(mcgrp, member);
1862
+ rd_free(mcgrp);
1863
+ }
1864
+
1865
+ /**
1866
+ * @brief A client connection closed, check if any cgrp has any state
1867
+ * for this connection that needs to be cleared.
1868
+ *
1869
+ * @param mcluster Mock cluster.
1870
+ * @param mconn Connection that was closed.
1871
+ */
1872
+ void rd_kafka_mock_cgrps_connection_closed(rd_kafka_mock_cluster_t *mcluster,
1873
+ rd_kafka_mock_connection_t *mconn) {
1874
+ rd_kafka_mock_cgrps_classic_connection_closed(mcluster, mconn);
1875
+ rd_kafka_mock_cgrps_consumer_connection_closed(mcluster, mconn);
1876
+ }