Files
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
c855109044 build(deps): bump github.com/twmb/franz-go/pkg/kadm (#5800)
Bumps [github.com/twmb/franz-go/pkg/kadm](https://github.com/twmb/franz-go) from 1.16.0 to 1.17.1.
- [Changelog](https://github.com/twmb/franz-go/blob/master/CHANGELOG.md)
- [Commits](https://github.com/twmb/franz-go/compare/v1.16.0...v1.17.1)

---
updated-dependencies:
- dependency-name: github.com/twmb/franz-go/pkg/kadm
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-05 08:38:06 +00:00

777 lines
26 KiB
Go

package kadm
import (
"context"
"errors"
"sort"
"github.com/twmb/franz-go/pkg/kerr"
"github.com/twmb/franz-go/pkg/kmsg"
)
// DescribedProducer contains the state of a transactional producer's last
// produce.
type DescribedProducer struct {
Leader int32 // Leader is the leader broker for this topic / partition.
Topic string // Topic is the topic being produced to.
Partition int32 // Partition is the partition being produced to.
ProducerID int64 // ProducerID is the producer ID that produced.
ProducerEpoch int16 // ProducerEpoch is the epoch that produced.
LastSequence int32 // LastSequence is the last sequence number the producer produced.
LastTimestamp int64 // LastTimestamp is the last time this producer produced.
CoordinatorEpoch int32 // CoordinatorEpoch is the epoch of the transactional coordinator for the last produce.
CurrentTxnStartOffset int64 // CurrentTxnStartOffset is the first offset in the transaction.
}
// Less returns whether the left described producer is less than the right,
// in order of:
//
// - Topic
// - Partition
// - ProducerID
// - ProducerEpoch
// - LastTimestamp
// - LastSequence
func (l *DescribedProducer) Less(r *DescribedProducer) bool {
if l.Topic < r.Topic {
return true
}
if l.Topic > r.Topic {
return false
}
if l.Partition < r.Partition {
return true
}
if l.Partition > r.Partition {
return false
}
if l.ProducerID < r.ProducerID {
return true
}
if l.ProducerID > r.ProducerID {
return false
}
if l.ProducerEpoch < r.ProducerEpoch {
return true
}
if l.ProducerEpoch > r.ProducerEpoch {
return false
}
if l.LastTimestamp < r.LastTimestamp {
return true
}
if l.LastTimestamp > r.LastTimestamp {
return false
}
return l.LastSequence < r.LastSequence
}
// DescribedProducers maps producer IDs to the full described producer.
type DescribedProducers map[int64]DescribedProducer
// Sorted returns the described producers sorted by topic, partition, and
// producer ID.
func (ds DescribedProducers) Sorted() []DescribedProducer {
var all []DescribedProducer
for _, d := range ds {
all = append(all, d)
}
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && (l.Partition < r.Partition || l.Partition == r.Partition && l.ProducerID < r.ProducerID)
})
return all
}
// Each calls fn for each described producer.
func (ds DescribedProducers) Each(fn func(DescribedProducer)) {
for _, d := range ds {
fn(d)
}
}
// DescribedProducersPartition is a partition whose producer's were described.
type DescribedProducersPartition struct {
Leader int32 // Leader is the leader broker for this topic / partition.
Topic string // Topic is the topic whose producer's were described.
Partition int32 // Partition is the partition whose producer's were described.
ActiveProducers DescribedProducers // ActiveProducers are producer's actively transactionally producing to this partition.
Err error // Err is non-nil if describing this partition failed.
ErrMessage string // ErrMessage a potential extra message describing any error.
}
// DescribedProducersPartitions contains partitions whose producer's were described.
type DescribedProducersPartitions map[int32]DescribedProducersPartition
// Sorted returns the described partitions sorted by topic and partition.
func (ds DescribedProducersPartitions) Sorted() []DescribedProducersPartition {
var all []DescribedProducersPartition
for _, d := range ds {
all = append(all, d)
}
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && l.Partition < r.Partition
})
return all
}
// SortedProducers returns all producers sorted first by partition, then by producer ID.
func (ds DescribedProducersPartitions) SortedProducers() []DescribedProducer {
var all []DescribedProducer
ds.EachProducer(func(d DescribedProducer) {
all = append(all, d)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && (l.Partition < r.Partition || l.Partition == r.Partition && l.ProducerID < r.ProducerID)
})
return all
}
// Each calls fn for each partition.
func (ds DescribedProducersPartitions) Each(fn func(DescribedProducersPartition)) {
for _, d := range ds {
fn(d)
}
}
// EachProducer calls fn for each producer in all partitions.
func (ds DescribedProducersPartitions) EachProducer(fn func(DescribedProducer)) {
for _, d := range ds {
for _, p := range d.ActiveProducers {
fn(p)
}
}
}
// DescribedProducersTopic contains topic partitions whose producer's were described.
type DescribedProducersTopic struct {
Topic string // Topic is the topic whose producer's were described.
Partitions DescribedProducersPartitions // Partitions are partitions whose producer's were described.
}
// DescribedProducersTopics contains topics whose producer's were described.
type DescribedProducersTopics map[string]DescribedProducersTopic
// Sorted returns the described topics sorted by topic.
func (ds DescribedProducersTopics) Sorted() []DescribedProducersTopic {
var all []DescribedProducersTopic
ds.Each(func(d DescribedProducersTopic) {
all = append(all, d)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic
})
return all
}
// SortedPartitions returns the described partitions sorted by topic and partition.
func (ds DescribedProducersTopics) SortedPartitions() []DescribedProducersPartition {
var all []DescribedProducersPartition
ds.EachPartition(func(d DescribedProducersPartition) {
all = append(all, d)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && l.Partition < r.Partition
})
return all
}
// SortedProducers returns all producers sorted first by partition, then by producer ID.
func (ds DescribedProducersTopics) SortedProducers() []DescribedProducer {
var all []DescribedProducer
ds.EachProducer(func(d DescribedProducer) {
all = append(all, d)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && (l.Partition < r.Partition || l.Partition == r.Partition && l.ProducerID < r.ProducerID)
})
return all
}
// Each calls fn for every topic.
func (ds DescribedProducersTopics) Each(fn func(DescribedProducersTopic)) {
for _, d := range ds {
fn(d)
}
}
// EachPartition calls fn for all topic partitions.
func (ds DescribedProducersTopics) EachPartition(fn func(DescribedProducersPartition)) {
for _, d := range ds {
for _, p := range d.Partitions {
fn(p)
}
}
}
// EachProducer calls fn for each producer in all topics and partitions.
func (ds DescribedProducersTopics) EachProducer(fn func(DescribedProducer)) {
for _, d := range ds {
for _, p := range d.Partitions {
for _, b := range p.ActiveProducers {
fn(b)
}
}
}
}
// DescribeProducers describes all producers that are transactionally producing
// to the requested topic set. This request can be used to detect hanging
// transactions or other transaction related problems. If the input set is
// empty, this requests data for all partitions.
//
// This may return *ShardErrors or *AuthError.
func (cl *Client) DescribeProducers(ctx context.Context, s TopicsSet) (DescribedProducersTopics, error) {
if len(s) == 0 {
m, err := cl.Metadata(ctx)
if err != nil {
return nil, err
}
s = m.Topics.TopicsSet()
} else if e := s.EmptyTopics(); len(e) > 0 {
m, err := cl.Metadata(ctx, e...)
if err != nil {
return nil, err
}
for t, ps := range m.Topics.TopicsSet() {
s[t] = ps
}
}
req := kmsg.NewPtrDescribeProducersRequest()
for _, t := range s.IntoList() {
rt := kmsg.NewDescribeProducersRequestTopic()
rt.Topic = t.Topic
rt.Partitions = t.Partitions
req.Topics = append(req.Topics, rt)
}
shards := cl.cl.RequestSharded(ctx, req)
dts := make(DescribedProducersTopics)
return dts, shardErrEachBroker(req, shards, func(b BrokerDetail, kr kmsg.Response) error {
resp := kr.(*kmsg.DescribeProducersResponse)
for _, rt := range resp.Topics {
dt, exists := dts[rt.Topic]
if !exists { // topic could be spread around brokers, we need to check existence
dt = DescribedProducersTopic{
Topic: rt.Topic,
Partitions: make(DescribedProducersPartitions),
}
dts[rt.Topic] = dt
}
dps := dt.Partitions
for _, rp := range rt.Partitions {
if err := maybeAuthErr(rp.ErrorCode); err != nil {
return err
}
drs := make(DescribedProducers)
dp := DescribedProducersPartition{
Leader: b.NodeID,
Topic: rt.Topic,
Partition: rp.Partition,
ActiveProducers: drs,
Err: kerr.ErrorForCode(rp.ErrorCode),
ErrMessage: unptrStr(rp.ErrorMessage),
}
dps[rp.Partition] = dp // one partition globally, no need to exist-check
for _, rr := range rp.ActiveProducers {
dr := DescribedProducer{
Leader: b.NodeID,
Topic: rt.Topic,
Partition: rp.Partition,
ProducerID: rr.ProducerID,
ProducerEpoch: int16(rr.ProducerEpoch),
LastSequence: rr.LastSequence,
LastTimestamp: rr.LastTimestamp,
CoordinatorEpoch: rr.CoordinatorEpoch,
CurrentTxnStartOffset: rr.CurrentTxnStartOffset,
}
drs[dr.ProducerID] = dr
}
}
}
return nil
})
}
// DescribedTransaction contains data from a describe transactions response for
// a single transactional ID.
type DescribedTransaction struct {
Coordinator int32 // Coordinator is the coordinator broker for this transactional ID.
TxnID string // TxnID is the name of this transactional ID.
State string // State is the state this transaction is in (Empty, Ongoing, PrepareCommit, PrepareAbort, CompleteCommit, CompleteAbort, Dead, PrepareEpochFence).
TimeoutMillis int32 // TimeoutMillis is the timeout of this transaction in milliseconds.
StartTimestamp int64 // StartTimestamp is millisecond when this transaction started.
ProducerID int64 // ProducerID is the ID in use by the transactional ID.
ProducerEpoch int16 // ProducerEpoch is the epoch associated with the produce rID.
// Topics is the set of partitions in the transaction, if active. When
// preparing to commit or abort, this includes only partitions which do
// not have markers. This does not include topics the user is not
// authorized to describe.
Topics TopicsSet
Err error // Err is non-nil if the transaction could not be described.
}
// DescribedTransactions contains information from a describe transactions
// response.
type DescribedTransactions map[string]DescribedTransaction
// Sorted returns all described transactions sorted by transactional ID.
func (ds DescribedTransactions) Sorted() []DescribedTransaction {
s := make([]DescribedTransaction, 0, len(ds))
for _, d := range ds {
s = append(s, d)
}
sort.Slice(s, func(i, j int) bool { return s[i].TxnID < s[j].TxnID })
return s
}
// Each calls fn for each described transaction.
func (ds DescribedTransactions) Each(fn func(DescribedTransaction)) {
for _, d := range ds {
fn(d)
}
}
// On calls fn for the transactional ID if it exists, returning the transaction
// and the error returned from fn. If fn is nil, this simply returns the
// transaction.
//
// The fn is given a shallow copy of the transaction. This function returns the
// copy as well; any modifications within fn are modifications on the returned
// copy. Modifications on a described transaction's inner fields are persisted
// to the original map (because slices are pointers).
//
// If the transaction does not exist, this returns
// kerr.TransactionalIDNotFound.
func (ds DescribedTransactions) On(txnID string, fn func(*DescribedTransaction) error) (DescribedTransaction, error) {
if len(ds) > 0 {
r, ok := ds[txnID]
if ok {
if fn == nil {
return r, nil
}
return r, fn(&r)
}
}
return DescribedTransaction{}, kerr.TransactionalIDNotFound
}
// TransactionalIDs returns a sorted list of all transactional IDs.
func (ds DescribedTransactions) TransactionalIDs() []string {
all := make([]string, 0, len(ds))
for t := range ds {
all = append(all, t)
}
sort.Strings(all)
return all
}
// DescribeTransactions describes either all transactional IDs specified, or
// all transactional IDs in the cluster if none are specified.
//
// This may return *ShardErrors or *AuthError.
//
// If no transactional IDs are specified and this method first lists
// transactional IDs, and listing IDs returns a *ShardErrors, this function
// describes all successfully listed IDs and appends the list shard errors to
// any describe shard errors.
//
// If only one ID is described, there will be at most one request issued and
// there is no need to deeply inspect the error.
func (cl *Client) DescribeTransactions(ctx context.Context, txnIDs ...string) (DescribedTransactions, error) {
var seList *ShardErrors
if len(txnIDs) == 0 {
listed, err := cl.ListTransactions(ctx, nil, nil)
switch {
case err == nil:
case errors.As(err, &seList):
default:
return nil, err
}
txnIDs = listed.TransactionalIDs()
if len(txnIDs) == 0 {
return nil, err
}
}
req := kmsg.NewPtrDescribeTransactionsRequest()
req.TransactionalIDs = txnIDs
shards := cl.cl.RequestSharded(ctx, req)
described := make(DescribedTransactions)
err := shardErrEachBroker(req, shards, func(b BrokerDetail, kr kmsg.Response) error {
resp := kr.(*kmsg.DescribeTransactionsResponse)
for _, rt := range resp.TransactionStates {
if err := maybeAuthErr(rt.ErrorCode); err != nil {
return err
}
t := DescribedTransaction{
Coordinator: b.NodeID,
TxnID: rt.TransactionalID,
State: rt.State,
TimeoutMillis: rt.TimeoutMillis,
StartTimestamp: rt.StartTimestamp,
ProducerID: rt.ProducerID,
ProducerEpoch: rt.ProducerEpoch,
Err: kerr.ErrorForCode(rt.ErrorCode),
}
for _, rtt := range rt.Topics {
t.Topics.Add(rtt.Topic, rtt.Partitions...)
}
described[t.TxnID] = t // txnID lives on one coordinator, no need to exist-check
}
return nil
})
var seDesc *ShardErrors
switch {
case err == nil:
return described, seList.into()
case errors.As(err, &seDesc):
if seList != nil {
seDesc.Errs = append(seList.Errs, seDesc.Errs...)
}
return described, seDesc.into()
default:
return nil, err
}
}
// ListedTransaction contains data from a list transactions response for a
// single transactional ID.
type ListedTransaction struct {
Coordinator int32 // Coordinator the coordinator broker for this transactional ID.
TxnID string // TxnID is the name of this transactional ID.
ProducerID int64 // ProducerID is the producer ID for this transaction.
State string // State is the state this transaction is in (Empty, Ongoing, PrepareCommit, PrepareAbort, CompleteCommit, CompleteAbort, Dead, PrepareEpochFence).
}
// ListedTransactions contains information from a list transactions response.
type ListedTransactions map[string]ListedTransaction
// Sorted returns all transactions sorted by transactional ID.
func (ls ListedTransactions) Sorted() []ListedTransaction {
s := make([]ListedTransaction, 0, len(ls))
for _, l := range ls {
s = append(s, l)
}
sort.Slice(s, func(i, j int) bool { return s[i].TxnID < s[j].TxnID })
return s
}
// Each calls fn for each listed transaction.
func (ls ListedTransactions) Each(fn func(ListedTransaction)) {
for _, l := range ls {
fn(l)
}
}
// TransactionalIDs returns a sorted list of all transactional IDs.
func (ls ListedTransactions) TransactionalIDs() []string {
all := make([]string, 0, len(ls))
for t := range ls {
all = append(all, t)
}
sort.Strings(all)
return all
}
// ListTransactions returns all transactions and their states in the cluster.
// Filter states can be used to return transactions only in the requested
// states. By default, this returns all transactions you have DESCRIBE access
// to. Producer IDs can be specified to filter for transactions from the given
// producer.
//
// This may return *ShardErrors or *AuthError.
func (cl *Client) ListTransactions(ctx context.Context, producerIDs []int64, filterStates []string) (ListedTransactions, error) {
return cl.ListTransactionsByTxPattern(ctx, producerIDs, filterStates, "")
}
// ListTransactionsByTxPattern returns all transactions and their states in the cluster.
// Filter states can be used to return transactions only in the requested
// states. By default, this returns all transactions you have DESCRIBE access
// to. Producer IDs can be specified to filter for transactions from the given
// producer. TxIDPatterns, a re2 regular expression, is used to further filter
// by transactional ID. This requires Kafka 4.1+.
//
// This may return *ShardErrors or *AuthError.
func (cl *Client) ListTransactionsByTxPattern(ctx context.Context, producerIDs []int64, filterStates []string, txIDPattern string) (ListedTransactions, error) {
req := kmsg.NewPtrListTransactionsRequest()
req.ProducerIDFilters = producerIDs
req.StateFilters = filterStates
if txIDPattern != "" {
req.TransactionalIDPattern = &txIDPattern
}
shards := cl.cl.RequestSharded(ctx, req)
list := make(ListedTransactions)
return list, shardErrEachBroker(req, shards, func(b BrokerDetail, kr kmsg.Response) error {
resp := kr.(*kmsg.ListTransactionsResponse)
if err := maybeAuthErr(resp.ErrorCode); err != nil {
return err
}
if err := kerr.ErrorForCode(resp.ErrorCode); err != nil {
return err
}
for _, t := range resp.TransactionStates {
list[t.TransactionalID] = ListedTransaction{ // txnID lives on one coordinator, no need to exist-check
Coordinator: b.NodeID,
TxnID: t.TransactionalID,
ProducerID: t.ProducerID,
State: t.TransactionState,
}
}
return nil
})
}
// TxnMarkers marks the end of a partition: the producer ID / epoch doing the
// writing, whether this is a commit, the coordinator epoch of the broker we
// are writing to (for fencing), and the topics and partitions that we are
// writing this abort or commit for.
//
// This is a very low level admin request and should likely be built from data
// in a DescribeProducers response. See KIP-664 if you are trying to use this.
type TxnMarkers struct {
ProducerID int64 // ProducerID is the ID to write markers for.
ProducerEpoch int16 // ProducerEpoch is the epoch to write markers for.
Commit bool // Commit is true if we are committing, false if we are aborting.
CoordinatorEpoch int32 // CoordinatorEpoch is the epoch of the transactional coordinator we are writing to; this is used for fencing.
Topics TopicsSet // Topics are topics and partitions to write markers for.
}
// TxnMarkersPartitionResponse is a response to a topic's partition within a
// single marker written.
type TxnMarkersPartitionResponse struct {
NodeID int32 // NodeID is the node that this marker was written to.
ProducerID int64 // ProducerID corresponds to the PID in the write marker request.
Topic string // Topic is the topic being responded to.
Partition int32 // Partition is the partition being responded to.
Err error // Err is non-nil if the WriteTxnMarkers request for this pid/topic/partition failed.
}
// TxnMarkersPartitionResponses contains per-partition responses to a
// WriteTxnMarkers request.
type TxnMarkersPartitionResponses map[int32]TxnMarkersPartitionResponse
// Sorted returns all partitions sorted by partition.
func (ps TxnMarkersPartitionResponses) Sorted() []TxnMarkersPartitionResponse {
var all []TxnMarkersPartitionResponse
ps.Each(func(p TxnMarkersPartitionResponse) {
all = append(all, p)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Partition < r.Partition
})
return all
}
// Each calls fn for each partition.
func (ps TxnMarkersPartitionResponses) Each(fn func(TxnMarkersPartitionResponse)) {
for _, p := range ps {
fn(p)
}
}
// TxnMarkersTopicResponse is a response to a topic within a single marker
// written.
type TxnMarkersTopicResponse struct {
ProducerID int64 // ProducerID corresponds to the PID in the write marker request.
Topic string // Topic is the topic being responded to.
Partitions TxnMarkersPartitionResponses // Partitions are the responses for partitions in this marker.
}
// TxnMarkersTopicResponses contains per-topic responses to a WriteTxnMarkers
// request.
type TxnMarkersTopicResponses map[string]TxnMarkersTopicResponse
// Sorted returns all topics sorted by topic.
func (ts TxnMarkersTopicResponses) Sorted() []TxnMarkersTopicResponse {
var all []TxnMarkersTopicResponse
ts.Each(func(t TxnMarkersTopicResponse) {
all = append(all, t)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic
})
return all
}
// SortedPartitions returns all topics sorted by topic then partition.
func (ts TxnMarkersTopicResponses) SortedPartitions() []TxnMarkersPartitionResponse {
var all []TxnMarkersPartitionResponse
ts.EachPartition(func(p TxnMarkersPartitionResponse) {
all = append(all, p)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.Topic < r.Topic || l.Topic == r.Topic && l.Partition < r.Partition
})
return all
}
// Each calls fn for each topic.
func (ts TxnMarkersTopicResponses) Each(fn func(TxnMarkersTopicResponse)) {
for _, t := range ts {
fn(t)
}
}
// EachPartition calls fn for every partition in all topics.
func (ts TxnMarkersTopicResponses) EachPartition(fn func(TxnMarkersPartitionResponse)) {
for _, t := range ts {
for _, p := range t.Partitions {
fn(p)
}
}
}
// TxnMarkersResponse is a response for a single marker written.
type TxnMarkersResponse struct {
ProducerID int64 // ProducerID corresponds to the PID in the write marker request.
Topics TxnMarkersTopicResponses // Topics contains the topics that markers were written for, for this ProducerID.
}
// TxnMarkersResponses contains per-partition-ID responses to a WriteTxnMarkers
// request.
type TxnMarkersResponses map[int64]TxnMarkersResponse
// Sorted returns all markers sorted by producer ID.
func (ms TxnMarkersResponses) Sorted() []TxnMarkersResponse {
var all []TxnMarkersResponse
ms.Each(func(m TxnMarkersResponse) {
all = append(all, m)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.ProducerID < r.ProducerID
})
return all
}
// SortedTopics returns all marker topics sorted by producer ID then topic.
func (ms TxnMarkersResponses) SortedTopics() []TxnMarkersTopicResponse {
var all []TxnMarkersTopicResponse
ms.EachTopic(func(t TxnMarkersTopicResponse) {
all = append(all, t)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.ProducerID < r.ProducerID || l.ProducerID == r.ProducerID && l.Topic < r.Topic
})
return all
}
// SortedPartitions returns all marker topic partitions sorted by producer ID
// then topic then partition.
func (ms TxnMarkersResponses) SortedPartitions() []TxnMarkersPartitionResponse {
var all []TxnMarkersPartitionResponse
ms.EachPartition(func(p TxnMarkersPartitionResponse) {
all = append(all, p)
})
sort.Slice(all, func(i, j int) bool {
l, r := all[i], all[j]
return l.ProducerID < r.ProducerID || l.ProducerID == r.ProducerID && l.Topic < r.Topic || l.Topic == r.Topic && l.Partition < r.Partition
})
return all
}
// Each calls fn for each marker response.
func (ms TxnMarkersResponses) Each(fn func(TxnMarkersResponse)) {
for _, m := range ms {
fn(m)
}
}
// EachTopic calls fn for every topic in all marker responses.
func (ms TxnMarkersResponses) EachTopic(fn func(TxnMarkersTopicResponse)) {
for _, m := range ms {
for _, t := range m.Topics {
fn(t)
}
}
}
// EachPartition calls fn for every partition in all topics in all marker
// responses.
func (ms TxnMarkersResponses) EachPartition(fn func(TxnMarkersPartitionResponse)) {
for _, m := range ms {
for _, t := range m.Topics {
for _, p := range t.Partitions {
fn(p)
}
}
}
}
// WriteTxnMarkers writes transaction markers to brokers. This is an advanced
// admin way to close out open transactions. See KIP-664 for more details.
//
// This may return *ShardErrors or *AuthError.
func (cl *Client) WriteTxnMarkers(ctx context.Context, markers ...TxnMarkers) (TxnMarkersResponses, error) {
req := kmsg.NewPtrWriteTxnMarkersRequest()
for _, m := range markers {
rm := kmsg.NewWriteTxnMarkersRequestMarker()
rm.ProducerID = m.ProducerID
rm.ProducerEpoch = m.ProducerEpoch
rm.Committed = m.Commit
rm.CoordinatorEpoch = m.CoordinatorEpoch
for t, ps := range m.Topics {
rt := kmsg.NewWriteTxnMarkersRequestMarkerTopic()
rt.Topic = t
for p := range ps {
rt.Partitions = append(rt.Partitions, p)
}
rm.Topics = append(rm.Topics, rt)
}
req.Markers = append(req.Markers, rm)
}
shards := cl.cl.RequestSharded(ctx, req)
rs := make(TxnMarkersResponses)
return rs, shardErrEachBroker(req, shards, func(b BrokerDetail, kr kmsg.Response) error {
resp := kr.(*kmsg.WriteTxnMarkersResponse)
for _, rm := range resp.Markers {
m, exists := rs[rm.ProducerID] // partitions are spread around, our marker could be split: we need to check existence
if !exists {
m = TxnMarkersResponse{
ProducerID: rm.ProducerID,
Topics: make(TxnMarkersTopicResponses),
}
rs[rm.ProducerID] = m
}
for _, rt := range rm.Topics {
t, exists := m.Topics[rt.Topic]
if !exists { // same thought
t = TxnMarkersTopicResponse{
ProducerID: rm.ProducerID,
Topic: rt.Topic,
Partitions: make(TxnMarkersPartitionResponses),
}
m.Topics[rt.Topic] = t
}
for _, rp := range rt.Partitions {
if err := maybeAuthErr(rp.ErrorCode); err != nil {
return err
}
t.Partitions[rp.Partition] = TxnMarkersPartitionResponse{ // one partition globally, no need to exist-check
NodeID: b.NodeID,
ProducerID: rm.ProducerID,
Topic: rt.Topic,
Partition: rp.Partition,
Err: kerr.ErrorForCode(rp.ErrorCode),
}
}
}
}
return nil
})
}