github.com/hoveychen/kafka-go@v0.4.42/describegroups.go (about) 1 package kafka 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "fmt" 8 "net" 9 10 "github.com/hoveychen/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 }