github.com/segmentio/kafka-go@v0.4.48-0.20240318174348-3f6244eb34fd/describegroups.go (about)

     1  package kafka
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"fmt"
     8  	"net"
     9  
    10  	"github.com/segmentio/kafka-go/protocol/describegroups"
    11  )
    12  
    13  // DescribeGroupsRequest is a request to the DescribeGroups API.
    14  type DescribeGroupsRequest struct {
    15  	// Addr is the address of the kafka broker to send the request to.
    16  	Addr net.Addr
    17  
    18  	// GroupIDs is a slice of groups to get details for.
    19  	GroupIDs []string
    20  }
    21  
    22  // DescribeGroupsResponse is a response from the DescribeGroups API.
    23  type DescribeGroupsResponse struct {
    24  	// Groups is a slice of details for the requested groups.
    25  	Groups []DescribeGroupsResponseGroup
    26  }
    27  
    28  // DescribeGroupsResponseGroup contains the response details for a single group.
    29  type DescribeGroupsResponseGroup struct {
    30  	// Error is set to a non-nil value if there was an error fetching the details
    31  	// for this group.
    32  	Error error
    33  
    34  	// GroupID is the ID of the group.
    35  	GroupID string
    36  
    37  	// GroupState is a description of the group state.
    38  	GroupState string
    39  
    40  	// Members contains details about each member of the group.
    41  	Members []DescribeGroupsResponseMember
    42  }
    43  
    44  // MemberInfo represents the membership information for a single group member.
    45  type DescribeGroupsResponseMember struct {
    46  	// MemberID is the ID of the group member.
    47  	MemberID string
    48  
    49  	// ClientID is the ID of the client that the group member is using.
    50  	ClientID string
    51  
    52  	// ClientHost is the host of the client that the group member is connecting from.
    53  	ClientHost string
    54  
    55  	// MemberMetadata contains metadata about this group member.
    56  	MemberMetadata DescribeGroupsResponseMemberMetadata
    57  
    58  	// MemberAssignments contains the topic partitions that this member is assigned to.
    59  	MemberAssignments DescribeGroupsResponseAssignments
    60  }
    61  
    62  // GroupMemberMetadata stores metadata associated with a group member.
    63  type DescribeGroupsResponseMemberMetadata struct {
    64  	// Version is the version of the metadata.
    65  	Version int
    66  
    67  	// Topics is the list of topics that the member is assigned to.
    68  	Topics []string
    69  
    70  	// UserData is the user data for the member.
    71  	UserData []byte
    72  
    73  	// OwnedPartitions contains the partitions owned by this group member; only set if
    74  	// consumers are using a cooperative rebalancing assignor protocol.
    75  	OwnedPartitions []DescribeGroupsResponseMemberMetadataOwnedPartition
    76  }
    77  
    78  type DescribeGroupsResponseMemberMetadataOwnedPartition struct {
    79  	// Topic is the name of the topic.
    80  	Topic string
    81  
    82  	// Partitions is the partitions that are owned by the group in the topic.
    83  	Partitions []int
    84  }
    85  
    86  // GroupMemberAssignmentsInfo stores the topic partition assignment data for a group member.
    87  type DescribeGroupsResponseAssignments struct {
    88  	// Version is the version of the assignments data.
    89  	Version int
    90  
    91  	// Topics contains the details of the partition assignments for each topic.
    92  	Topics []GroupMemberTopic
    93  
    94  	// UserData is the user data for the member.
    95  	UserData []byte
    96  }
    97  
    98  // GroupMemberTopic is a mapping from a topic to a list of partitions in the topic. It is used
    99  // to represent the topic partitions that have been assigned to a group member.
   100  type GroupMemberTopic struct {
   101  	// Topic is the name of the topic.
   102  	Topic string
   103  
   104  	// Partitions is a slice of partition IDs that this member is assigned to in the topic.
   105  	Partitions []int
   106  }
   107  
   108  // DescribeGroups calls the Kafka DescribeGroups API to get information about one or more
   109  // consumer groups. See https://kafka.apache.org/protocol#The_Messages_DescribeGroups for
   110  // more information.
   111  func (c *Client) DescribeGroups(
   112  	ctx context.Context,
   113  	req *DescribeGroupsRequest,
   114  ) (*DescribeGroupsResponse, error) {
   115  	protoResp, err := c.roundTrip(
   116  		ctx,
   117  		req.Addr,
   118  		&describegroups.Request{
   119  			Groups: req.GroupIDs,
   120  		},
   121  	)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	apiResp := protoResp.(*describegroups.Response)
   126  	resp := &DescribeGroupsResponse{}
   127  
   128  	for _, apiGroup := range apiResp.Groups {
   129  		group := DescribeGroupsResponseGroup{
   130  			Error:      makeError(apiGroup.ErrorCode, ""),
   131  			GroupID:    apiGroup.GroupID,
   132  			GroupState: apiGroup.GroupState,
   133  		}
   134  
   135  		for _, member := range apiGroup.Members {
   136  			decodedMetadata, err := decodeMemberMetadata(member.MemberMetadata)
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  			decodedAssignments, err := decodeMemberAssignments(member.MemberAssignment)
   141  			if err != nil {
   142  				return nil, err
   143  			}
   144  
   145  			group.Members = append(group.Members, DescribeGroupsResponseMember{
   146  				MemberID:          member.MemberID,
   147  				ClientID:          member.ClientID,
   148  				ClientHost:        member.ClientHost,
   149  				MemberAssignments: decodedAssignments,
   150  				MemberMetadata:    decodedMetadata,
   151  			})
   152  		}
   153  		resp.Groups = append(resp.Groups, group)
   154  	}
   155  
   156  	return resp, nil
   157  }
   158  
   159  // decodeMemberMetadata converts raw metadata bytes to a
   160  // DescribeGroupsResponseMemberMetadata struct.
   161  //
   162  // See https://github.com/apache/kafka/blob/2.4/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerProtocol.java#L49
   163  // for protocol details.
   164  func decodeMemberMetadata(rawMetadata []byte) (DescribeGroupsResponseMemberMetadata, error) {
   165  	mm := DescribeGroupsResponseMemberMetadata{}
   166  
   167  	if len(rawMetadata) == 0 {
   168  		return mm, nil
   169  	}
   170  
   171  	buf := bytes.NewBuffer(rawMetadata)
   172  	bufReader := bufio.NewReader(buf)
   173  	remain := len(rawMetadata)
   174  
   175  	var err error
   176  	var version16 int16
   177  
   178  	if remain, err = readInt16(bufReader, remain, &version16); err != nil {
   179  		return mm, err
   180  	}
   181  	mm.Version = int(version16)
   182  
   183  	if remain, err = readStringArray(bufReader, remain, &mm.Topics); err != nil {
   184  		return mm, err
   185  	}
   186  	if remain, err = readBytes(bufReader, remain, &mm.UserData); err != nil {
   187  		return mm, err
   188  	}
   189  
   190  	if mm.Version == 1 && remain > 0 {
   191  		fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) {
   192  			op := DescribeGroupsResponseMemberMetadataOwnedPartition{}
   193  			if fnRemain, fnErr = readString(r, size, &op.Topic); fnErr != nil {
   194  				return
   195  			}
   196  
   197  			ps := []int32{}
   198  			if fnRemain, fnErr = readInt32Array(r, fnRemain, &ps); fnErr != nil {
   199  				return
   200  			}
   201  
   202  			for _, p := range ps {
   203  				op.Partitions = append(op.Partitions, int(p))
   204  			}
   205  
   206  			mm.OwnedPartitions = append(mm.OwnedPartitions, op)
   207  			return
   208  		}
   209  
   210  		if remain, err = readArrayWith(bufReader, remain, fn); err != nil {
   211  			return mm, err
   212  		}
   213  	}
   214  
   215  	if remain != 0 {
   216  		return mm, fmt.Errorf("Got non-zero number of bytes remaining: %d", remain)
   217  	}
   218  
   219  	return mm, nil
   220  }
   221  
   222  // decodeMemberAssignments converts raw assignment bytes to a DescribeGroupsResponseAssignments
   223  // struct.
   224  //
   225  // See https://github.com/apache/kafka/blob/2.4/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerProtocol.java#L49
   226  // for protocol details.
   227  func decodeMemberAssignments(rawAssignments []byte) (DescribeGroupsResponseAssignments, error) {
   228  	ma := DescribeGroupsResponseAssignments{}
   229  
   230  	if len(rawAssignments) == 0 {
   231  		return ma, nil
   232  	}
   233  
   234  	buf := bytes.NewBuffer(rawAssignments)
   235  	bufReader := bufio.NewReader(buf)
   236  	remain := len(rawAssignments)
   237  
   238  	var err error
   239  	var version16 int16
   240  
   241  	if remain, err = readInt16(bufReader, remain, &version16); err != nil {
   242  		return ma, err
   243  	}
   244  	ma.Version = int(version16)
   245  
   246  	fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) {
   247  		item := GroupMemberTopic{}
   248  
   249  		if fnRemain, fnErr = readString(r, size, &item.Topic); fnErr != nil {
   250  			return
   251  		}
   252  
   253  		partitions := []int32{}
   254  
   255  		if fnRemain, fnErr = readInt32Array(r, fnRemain, &partitions); fnErr != nil {
   256  			return
   257  		}
   258  		for _, partition := range partitions {
   259  			item.Partitions = append(item.Partitions, int(partition))
   260  		}
   261  
   262  		ma.Topics = append(ma.Topics, item)
   263  		return
   264  	}
   265  	if remain, err = readArrayWith(bufReader, remain, fn); err != nil {
   266  		return ma, err
   267  	}
   268  
   269  	if remain, err = readBytes(bufReader, remain, &ma.UserData); err != nil {
   270  		return ma, err
   271  	}
   272  
   273  	if remain != 0 {
   274  		return ma, fmt.Errorf("Got non-zero number of bytes remaining: %d", remain)
   275  	}
   276  
   277  	return ma, nil
   278  }
   279  
   280  // readInt32Array reads an array of int32s. It's adapted from the implementation of
   281  // readStringArray.
   282  func readInt32Array(r *bufio.Reader, sz int, v *[]int32) (remain int, err error) {
   283  	var content []int32
   284  	fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) {
   285  		var value int32
   286  		if fnRemain, fnErr = readInt32(r, size, &value); fnErr != nil {
   287  			return
   288  		}
   289  		content = append(content, value)
   290  		return
   291  	}
   292  	if remain, err = readArrayWith(r, sz, fn); err != nil {
   293  		return
   294  	}
   295  
   296  	*v = content
   297  	return
   298  }