github.com/rbisecke/kafka-go@v0.4.27/offsetfetch.go (about)

     1  package kafka
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"net"
     8  	"time"
     9  
    10  	"github.com/rbisecke/kafka-go/protocol/offsetfetch"
    11  )
    12  
    13  // OffsetFetchRequest represents a request sent to a kafka broker to read the
    14  // currently committed offsets of topic partitions.
    15  type OffsetFetchRequest struct {
    16  	// Address of the kafka broker to send the request to.
    17  	Addr net.Addr
    18  
    19  	// ID of the consumer group to retrieve the offsets for.
    20  	GroupID string
    21  
    22  	// Set of topic partitions to retrieve the offsets for.
    23  	Topics map[string][]int
    24  }
    25  
    26  // OffsetFetchResponse represents a response from a kafka broker to an offset
    27  // fetch request.
    28  type OffsetFetchResponse struct {
    29  	// The amount of time that the broker throttled the request.
    30  	Throttle time.Duration
    31  
    32  	// Set of topic partitions that the kafka broker has returned offsets for.
    33  	Topics map[string][]OffsetFetchPartition
    34  
    35  	// An error that may have occurred while attempting to retrieve consumer
    36  	// group offsets.
    37  	//
    38  	// The error contains both the kafka error code, and an error message
    39  	// returned by the kafka broker. Programs may use the standard errors.Is
    40  	// function to test the error against kafka error codes.
    41  	Error error
    42  }
    43  
    44  // OffsetFetchPartition represents the state of a single partition in a consumer
    45  // group.
    46  type OffsetFetchPartition struct {
    47  	// ID of the partition.
    48  	Partition int
    49  
    50  	// Last committed offsets on the partition when the request was served by
    51  	// the kafka broker.
    52  	CommittedOffset int64
    53  
    54  	// Consumer group metadata for this partition.
    55  	Metadata string
    56  
    57  	// An error that may have occurred while attempting to retrieve consumer
    58  	// group offsets for this partition.
    59  	//
    60  	// The error contains both the kafka error code, and an error message
    61  	// returned by the kafka broker. Programs may use the standard errors.Is
    62  	// function to test the error against kafka error codes.
    63  	Error error
    64  }
    65  
    66  // OffsetFetch sends an offset fetch request to a kafka broker and returns the
    67  // response.
    68  func (c *Client) OffsetFetch(ctx context.Context, req *OffsetFetchRequest) (*OffsetFetchResponse, error) {
    69  	topics := make([]offsetfetch.RequestTopic, 0, len(req.Topics))
    70  
    71  	for topicName, partitions := range req.Topics {
    72  		indexes := make([]int32, len(partitions))
    73  
    74  		for i, p := range partitions {
    75  			indexes[i] = int32(p)
    76  		}
    77  
    78  		topics = append(topics, offsetfetch.RequestTopic{
    79  			Name:             topicName,
    80  			PartitionIndexes: indexes,
    81  		})
    82  	}
    83  
    84  	m, err := c.roundTrip(ctx, req.Addr, &offsetfetch.Request{
    85  		GroupID: req.GroupID,
    86  		Topics:  topics,
    87  	})
    88  
    89  	if err != nil {
    90  		return nil, fmt.Errorf("kafka.(*Client).OffsetFetch: %w", err)
    91  	}
    92  
    93  	res := m.(*offsetfetch.Response)
    94  	ret := &OffsetFetchResponse{
    95  		Throttle: makeDuration(res.ThrottleTimeMs),
    96  		Topics:   make(map[string][]OffsetFetchPartition, len(res.Topics)),
    97  		Error:    makeError(res.ErrorCode, ""),
    98  	}
    99  
   100  	for _, t := range res.Topics {
   101  		partitions := make([]OffsetFetchPartition, len(t.Partitions))
   102  
   103  		for i, p := range t.Partitions {
   104  			partitions[i] = OffsetFetchPartition{
   105  				Partition:       int(p.PartitionIndex),
   106  				CommittedOffset: p.CommittedOffset,
   107  				Metadata:        p.Metadata,
   108  				Error:           makeError(p.ErrorCode, ""),
   109  			}
   110  		}
   111  
   112  		ret.Topics[t.Name] = partitions
   113  	}
   114  
   115  	return ret, nil
   116  }
   117  
   118  type offsetFetchRequestV1Topic struct {
   119  	// Topic name
   120  	Topic string
   121  
   122  	// Partitions to fetch offsets
   123  	Partitions []int32
   124  }
   125  
   126  func (t offsetFetchRequestV1Topic) size() int32 {
   127  	return sizeofString(t.Topic) +
   128  		sizeofInt32Array(t.Partitions)
   129  }
   130  
   131  func (t offsetFetchRequestV1Topic) writeTo(wb *writeBuffer) {
   132  	wb.writeString(t.Topic)
   133  	wb.writeInt32Array(t.Partitions)
   134  }
   135  
   136  type offsetFetchRequestV1 struct {
   137  	// GroupID holds the unique group identifier
   138  	GroupID string
   139  
   140  	// Topics to fetch offsets.
   141  	Topics []offsetFetchRequestV1Topic
   142  }
   143  
   144  func (t offsetFetchRequestV1) size() int32 {
   145  	return sizeofString(t.GroupID) +
   146  		sizeofArray(len(t.Topics), func(i int) int32 { return t.Topics[i].size() })
   147  }
   148  
   149  func (t offsetFetchRequestV1) writeTo(wb *writeBuffer) {
   150  	wb.writeString(t.GroupID)
   151  	wb.writeArray(len(t.Topics), func(i int) { t.Topics[i].writeTo(wb) })
   152  }
   153  
   154  type offsetFetchResponseV1PartitionResponse struct {
   155  	// Partition ID
   156  	Partition int32
   157  
   158  	// Offset of last committed message
   159  	Offset int64
   160  
   161  	// Metadata client wants to keep
   162  	Metadata string
   163  
   164  	// ErrorCode holds response error code
   165  	ErrorCode int16
   166  }
   167  
   168  func (t offsetFetchResponseV1PartitionResponse) size() int32 {
   169  	return sizeofInt32(t.Partition) +
   170  		sizeofInt64(t.Offset) +
   171  		sizeofString(t.Metadata) +
   172  		sizeofInt16(t.ErrorCode)
   173  }
   174  
   175  func (t offsetFetchResponseV1PartitionResponse) writeTo(wb *writeBuffer) {
   176  	wb.writeInt32(t.Partition)
   177  	wb.writeInt64(t.Offset)
   178  	wb.writeString(t.Metadata)
   179  	wb.writeInt16(t.ErrorCode)
   180  }
   181  
   182  func (t *offsetFetchResponseV1PartitionResponse) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   183  	if remain, err = readInt32(r, size, &t.Partition); err != nil {
   184  		return
   185  	}
   186  	if remain, err = readInt64(r, remain, &t.Offset); err != nil {
   187  		return
   188  	}
   189  	if remain, err = readString(r, remain, &t.Metadata); err != nil {
   190  		return
   191  	}
   192  	if remain, err = readInt16(r, remain, &t.ErrorCode); err != nil {
   193  		return
   194  	}
   195  	return
   196  }
   197  
   198  type offsetFetchResponseV1Response struct {
   199  	// Topic name
   200  	Topic string
   201  
   202  	// PartitionResponses holds offsets by partition
   203  	PartitionResponses []offsetFetchResponseV1PartitionResponse
   204  }
   205  
   206  func (t offsetFetchResponseV1Response) size() int32 {
   207  	return sizeofString(t.Topic) +
   208  		sizeofArray(len(t.PartitionResponses), func(i int) int32 { return t.PartitionResponses[i].size() })
   209  }
   210  
   211  func (t offsetFetchResponseV1Response) writeTo(wb *writeBuffer) {
   212  	wb.writeString(t.Topic)
   213  	wb.writeArray(len(t.PartitionResponses), func(i int) { t.PartitionResponses[i].writeTo(wb) })
   214  }
   215  
   216  func (t *offsetFetchResponseV1Response) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   217  	if remain, err = readString(r, size, &t.Topic); err != nil {
   218  		return
   219  	}
   220  
   221  	fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) {
   222  		item := offsetFetchResponseV1PartitionResponse{}
   223  		if fnRemain, fnErr = (&item).readFrom(r, size); err != nil {
   224  			return
   225  		}
   226  		t.PartitionResponses = append(t.PartitionResponses, item)
   227  		return
   228  	}
   229  	if remain, err = readArrayWith(r, remain, fn); err != nil {
   230  		return
   231  	}
   232  
   233  	return
   234  }
   235  
   236  type offsetFetchResponseV1 struct {
   237  	// Responses holds topic partition offsets
   238  	Responses []offsetFetchResponseV1Response
   239  }
   240  
   241  func (t offsetFetchResponseV1) size() int32 {
   242  	return sizeofArray(len(t.Responses), func(i int) int32 { return t.Responses[i].size() })
   243  }
   244  
   245  func (t offsetFetchResponseV1) writeTo(wb *writeBuffer) {
   246  	wb.writeArray(len(t.Responses), func(i int) { t.Responses[i].writeTo(wb) })
   247  }
   248  
   249  func (t *offsetFetchResponseV1) readFrom(r *bufio.Reader, size int) (remain int, err error) {
   250  	fn := func(r *bufio.Reader, withSize int) (fnRemain int, fnErr error) {
   251  		item := offsetFetchResponseV1Response{}
   252  		if fnRemain, fnErr = (&item).readFrom(r, withSize); fnErr != nil {
   253  			return
   254  		}
   255  		t.Responses = append(t.Responses, item)
   256  		return
   257  	}
   258  	if remain, err = readArrayWith(r, size, fn); err != nil {
   259  		return
   260  	}
   261  
   262  	return
   263  }
   264  
   265  func findOffset(topic string, partition int32, response offsetFetchResponseV1) (int64, bool) {
   266  	for _, r := range response.Responses {
   267  		if r.Topic != topic {
   268  			continue
   269  		}
   270  
   271  		for _, pr := range r.PartitionResponses {
   272  			if pr.Partition == partition {
   273  				return pr.Offset, true
   274  			}
   275  		}
   276  	}
   277  
   278  	return 0, false
   279  }