mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
c855109044
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>
777 lines
26 KiB
Go
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
|
|
})
|
|
}
|