github.com/rbisecke/kafka-go@v0.4.27/describegroups.go (about) 1 package kafka 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "fmt" 8 "net" 9 10 "github.com/rbisecke/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 // readFrom decodes an owned partition item from the member metadata. 160 func (t *DescribeGroupsResponseMemberMetadataOwnedPartition) readFrom(r *bufio.Reader, size int) (remain int, err error) { 161 if remain, err = readString(r, size, &t.Topic); err != nil { 162 return 163 } 164 partitions := []int32{} 165 166 if remain, err = readInt32Array(r, remain, &partitions); err != nil { 167 return 168 } 169 for _, partition := range partitions { 170 t.Partitions = append(t.Partitions, int(partition)) 171 } 172 173 return 174 } 175 176 // decodeMemberMetadata converts raw metadata bytes to a 177 // DescribeGroupsResponseMemberMetadata struct. 178 // 179 // See https://github.com/apache/kafka/blob/2.4/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerProtocol.java#L49 180 // for protocol details. 181 func decodeMemberMetadata(rawMetadata []byte) (DescribeGroupsResponseMemberMetadata, error) { 182 mm := DescribeGroupsResponseMemberMetadata{} 183 184 if len(rawMetadata) == 0 { 185 return mm, nil 186 } 187 188 buf := bytes.NewBuffer(rawMetadata) 189 bufReader := bufio.NewReader(buf) 190 remain := len(rawMetadata) 191 192 var err error 193 var version16 int16 194 195 if remain, err = readInt16(bufReader, remain, &version16); err != nil { 196 return mm, err 197 } 198 mm.Version = int(version16) 199 200 if remain, err = readStringArray(bufReader, remain, &mm.Topics); err != nil { 201 return mm, err 202 } 203 if remain, err = readBytes(bufReader, remain, &mm.UserData); err != nil { 204 return mm, err 205 } 206 207 if mm.Version == 1 && remain > 0 { 208 fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) { 209 op := DescribeGroupsResponseMemberMetadataOwnedPartition{} 210 if fnRemain, fnErr = readString(r, size, &op.Topic); fnErr != nil { 211 return 212 } 213 214 ps := []int32{} 215 if fnRemain, fnErr = readInt32Array(r, fnRemain, &ps); fnErr != nil { 216 return 217 } 218 219 for _, p := range ps { 220 op.Partitions = append(op.Partitions, int(p)) 221 } 222 223 mm.OwnedPartitions = append(mm.OwnedPartitions, op) 224 return 225 } 226 227 if remain, err = readArrayWith(bufReader, remain, fn); err != nil { 228 return mm, err 229 } 230 } 231 232 if remain != 0 { 233 return mm, fmt.Errorf("Got non-zero number of bytes remaining: %d", remain) 234 } 235 236 return mm, nil 237 } 238 239 // decodeMemberAssignments converts raw assignment bytes to a DescribeGroupsResponseAssignments 240 // struct. 241 // 242 // See https://github.com/apache/kafka/blob/2.4/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ConsumerProtocol.java#L49 243 // for protocol details. 244 func decodeMemberAssignments(rawAssignments []byte) (DescribeGroupsResponseAssignments, error) { 245 ma := DescribeGroupsResponseAssignments{} 246 247 if len(rawAssignments) == 0 { 248 return ma, nil 249 } 250 251 buf := bytes.NewBuffer(rawAssignments) 252 bufReader := bufio.NewReader(buf) 253 remain := len(rawAssignments) 254 255 var err error 256 var version16 int16 257 258 if remain, err = readInt16(bufReader, remain, &version16); err != nil { 259 return ma, err 260 } 261 ma.Version = int(version16) 262 263 fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) { 264 item := GroupMemberTopic{} 265 266 if fnRemain, fnErr = readString(r, size, &item.Topic); fnErr != nil { 267 return 268 } 269 270 partitions := []int32{} 271 272 if fnRemain, fnErr = readInt32Array(r, fnRemain, &partitions); fnErr != nil { 273 return 274 } 275 for _, partition := range partitions { 276 item.Partitions = append(item.Partitions, int(partition)) 277 } 278 279 ma.Topics = append(ma.Topics, item) 280 return 281 } 282 if remain, err = readArrayWith(bufReader, remain, fn); err != nil { 283 return ma, err 284 } 285 286 if remain, err = readBytes(bufReader, remain, &ma.UserData); err != nil { 287 return ma, err 288 } 289 290 if remain != 0 { 291 return ma, fmt.Errorf("Got non-zero number of bytes remaining: %d", remain) 292 } 293 294 return ma, nil 295 } 296 297 // readInt32Array reads an array of int32s. It's adapted from the implementation of 298 // readStringArray. 299 func readInt32Array(r *bufio.Reader, sz int, v *[]int32) (remain int, err error) { 300 var content []int32 301 fn := func(r *bufio.Reader, size int) (fnRemain int, fnErr error) { 302 var value int32 303 if fnRemain, fnErr = readInt32(r, size, &value); fnErr != nil { 304 return 305 } 306 content = append(content, value) 307 return 308 } 309 if remain, err = readArrayWith(r, sz, fn); err != nil { 310 return 311 } 312 313 *v = content 314 return 315 }