Browse Source

fix remaining tests

pull/7231/head
chrislu 2 months ago
parent
commit
6eafc87413
  1. 114
      weed/mq/kafka/protocol/consumer_coordination.go
  2. 13
      weed/mq/kafka/protocol/fetch.go

114
weed/mq/kafka/protocol/consumer_coordination.go

@ -4,7 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"time" "time"
"github.com/seaweedfs/seaweedfs/weed/mq/kafka/consumer" "github.com/seaweedfs/seaweedfs/weed/mq/kafka/consumer"
) )
@ -13,9 +13,9 @@ import (
// HeartbeatRequest represents a Heartbeat request from a Kafka client // HeartbeatRequest represents a Heartbeat request from a Kafka client
type HeartbeatRequest struct { type HeartbeatRequest struct {
GroupID string
GenerationID int32
MemberID string
GroupID string
GenerationID int32
MemberID string
GroupInstanceID string // Optional static membership ID GroupInstanceID string // Optional static membership ID
} }
@ -30,10 +30,10 @@ type HeartbeatResponse struct {
// LeaveGroupRequest represents a LeaveGroup request from a Kafka client // LeaveGroupRequest represents a LeaveGroup request from a Kafka client
type LeaveGroupRequest struct { type LeaveGroupRequest struct {
GroupID string
MemberID string
GroupInstanceID string // Optional static membership ID
Members []LeaveGroupMember // For newer versions, can leave multiple members
GroupID string
MemberID string
GroupInstanceID string // Optional static membership ID
Members []LeaveGroupMember // For newer versions, can leave multiple members
} }
// LeaveGroupMember represents a member leaving the group (for batch departures) // LeaveGroupMember represents a member leaving the group (for batch departures)
@ -69,38 +69,38 @@ func (h *Handler) handleHeartbeat(correlationID uint32, requestBody []byte) ([]b
if err != nil { if err != nil {
return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
// Validate request // Validate request
if request.GroupID == "" || request.MemberID == "" { if request.GroupID == "" || request.MemberID == "" {
return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
// Get consumer group // Get consumer group
group := h.groupCoordinator.GetGroup(request.GroupID) group := h.groupCoordinator.GetGroup(request.GroupID)
if group == nil { if group == nil {
return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
group.Mu.Lock() group.Mu.Lock()
defer group.Mu.Unlock() defer group.Mu.Unlock()
// Update group's last activity // Update group's last activity
group.LastActivity = time.Now() group.LastActivity = time.Now()
// Validate member exists // Validate member exists
member, exists := group.Members[request.MemberID] member, exists := group.Members[request.MemberID]
if !exists { if !exists {
return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeUnknownMemberID), nil return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeUnknownMemberID), nil
} }
// Validate generation // Validate generation
if request.GenerationID != group.Generation { if request.GenerationID != group.Generation {
return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeIllegalGeneration), nil return h.buildHeartbeatErrorResponse(correlationID, ErrorCodeIllegalGeneration), nil
} }
// Update member's last heartbeat // Update member's last heartbeat
member.LastHeartbeat = time.Now() member.LastHeartbeat = time.Now()
// Check if rebalancing is in progress // Check if rebalancing is in progress
var errorCode int16 = ErrorCodeNone var errorCode int16 = ErrorCodeNone
switch group.State { switch group.State {
@ -116,13 +116,13 @@ func (h *Handler) handleHeartbeat(correlationID uint32, requestBody []byte) ([]b
// Normal case - heartbeat accepted // Normal case - heartbeat accepted
errorCode = ErrorCodeNone errorCode = ErrorCodeNone
} }
// Build successful response // Build successful response
response := HeartbeatResponse{ response := HeartbeatResponse{
CorrelationID: correlationID, CorrelationID: correlationID,
ErrorCode: errorCode, ErrorCode: errorCode,
} }
return h.buildHeartbeatResponse(response), nil return h.buildHeartbeatResponse(response), nil
} }
@ -132,33 +132,33 @@ func (h *Handler) handleLeaveGroup(correlationID uint32, requestBody []byte) ([]
if err != nil { if err != nil {
return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
// Validate request // Validate request
if request.GroupID == "" || request.MemberID == "" { if request.GroupID == "" || request.MemberID == "" {
return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
// Get consumer group // Get consumer group
group := h.groupCoordinator.GetGroup(request.GroupID) group := h.groupCoordinator.GetGroup(request.GroupID)
if group == nil { if group == nil {
return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeInvalidGroupID), nil
} }
group.Mu.Lock() group.Mu.Lock()
defer group.Mu.Unlock() defer group.Mu.Unlock()
// Update group's last activity // Update group's last activity
group.LastActivity = time.Now() group.LastActivity = time.Now()
// Validate member exists // Validate member exists
_, exists := group.Members[request.MemberID] _, exists := group.Members[request.MemberID]
if !exists { if !exists {
return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeUnknownMemberID), nil return h.buildLeaveGroupErrorResponse(correlationID, ErrorCodeUnknownMemberID), nil
} }
// Remove the member from the group // Remove the member from the group
delete(group.Members, request.MemberID) delete(group.Members, request.MemberID)
// Update group state based on remaining members // Update group state based on remaining members
if len(group.Members) == 0 { if len(group.Members) == 0 {
// Group becomes empty // Group becomes empty
@ -169,7 +169,7 @@ func (h *Handler) handleLeaveGroup(correlationID uint32, requestBody []byte) ([]
// Trigger rebalancing for remaining members // Trigger rebalancing for remaining members
group.State = consumer.GroupStatePreparingRebalance group.State = consumer.GroupStatePreparingRebalance
group.Generation++ group.Generation++
// If the leaving member was the leader, select a new leader // If the leaving member was the leader, select a new leader
if group.Leader == request.MemberID { if group.Leader == request.MemberID {
// Select first remaining member as new leader // Select first remaining member as new leader
@ -178,16 +178,16 @@ func (h *Handler) handleLeaveGroup(correlationID uint32, requestBody []byte) ([]
break break
} }
} }
// Mark remaining members as pending to trigger rebalancing // Mark remaining members as pending to trigger rebalancing
for _, member := range group.Members { for _, member := range group.Members {
member.State = consumer.MemberStatePending member.State = consumer.MemberStatePending
} }
} }
// Update group's subscribed topics (may have changed with member leaving) // Update group's subscribed topics (may have changed with member leaving)
h.updateGroupSubscriptionFromMembers(group) h.updateGroupSubscriptionFromMembers(group)
// Build successful response // Build successful response
response := LeaveGroupResponse{ response := LeaveGroupResponse{
CorrelationID: correlationID, CorrelationID: correlationID,
@ -200,7 +200,7 @@ func (h *Handler) handleLeaveGroup(correlationID uint32, requestBody []byte) ([]
}, },
}, },
} }
return h.buildLeaveGroupResponse(response), nil return h.buildLeaveGroupResponse(response), nil
} }
@ -208,9 +208,9 @@ func (h *Handler) parseHeartbeatRequest(data []byte) (*HeartbeatRequest, error)
if len(data) < 8 { if len(data) < 8 {
return nil, fmt.Errorf("request too short") return nil, fmt.Errorf("request too short")
} }
offset := 0 offset := 0
// GroupID (string) // GroupID (string)
groupIDLength := int(binary.BigEndian.Uint16(data[offset:])) groupIDLength := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2 offset += 2
@ -219,14 +219,14 @@ func (h *Handler) parseHeartbeatRequest(data []byte) (*HeartbeatRequest, error)
} }
groupID := string(data[offset : offset+groupIDLength]) groupID := string(data[offset : offset+groupIDLength])
offset += groupIDLength offset += groupIDLength
// Generation ID (4 bytes) // Generation ID (4 bytes)
if offset+4 > len(data) { if offset+4 > len(data) {
return nil, fmt.Errorf("missing generation ID") return nil, fmt.Errorf("missing generation ID")
} }
generationID := int32(binary.BigEndian.Uint32(data[offset:])) generationID := int32(binary.BigEndian.Uint32(data[offset:]))
offset += 4 offset += 4
// MemberID (string) // MemberID (string)
if offset+2 > len(data) { if offset+2 > len(data) {
return nil, fmt.Errorf("missing member ID length") return nil, fmt.Errorf("missing member ID length")
@ -238,7 +238,7 @@ func (h *Handler) parseHeartbeatRequest(data []byte) (*HeartbeatRequest, error)
} }
memberID := string(data[offset : offset+memberIDLength]) memberID := string(data[offset : offset+memberIDLength])
offset += memberIDLength offset += memberIDLength
return &HeartbeatRequest{ return &HeartbeatRequest{
GroupID: groupID, GroupID: groupID,
GenerationID: generationID, GenerationID: generationID,
@ -251,9 +251,9 @@ func (h *Handler) parseLeaveGroupRequest(data []byte) (*LeaveGroupRequest, error
if len(data) < 4 { if len(data) < 4 {
return nil, fmt.Errorf("request too short") return nil, fmt.Errorf("request too short")
} }
offset := 0 offset := 0
// GroupID (string) // GroupID (string)
groupIDLength := int(binary.BigEndian.Uint16(data[offset:])) groupIDLength := int(binary.BigEndian.Uint16(data[offset:]))
offset += 2 offset += 2
@ -262,7 +262,7 @@ func (h *Handler) parseLeaveGroupRequest(data []byte) (*LeaveGroupRequest, error
} }
groupID := string(data[offset : offset+groupIDLength]) groupID := string(data[offset : offset+groupIDLength])
offset += groupIDLength offset += groupIDLength
// MemberID (string) // MemberID (string)
if offset+2 > len(data) { if offset+2 > len(data) {
return nil, fmt.Errorf("missing member ID length") return nil, fmt.Errorf("missing member ID length")
@ -274,31 +274,31 @@ func (h *Handler) parseLeaveGroupRequest(data []byte) (*LeaveGroupRequest, error
} }
memberID := string(data[offset : offset+memberIDLength]) memberID := string(data[offset : offset+memberIDLength])
offset += memberIDLength offset += memberIDLength
return &LeaveGroupRequest{ return &LeaveGroupRequest{
GroupID: groupID, GroupID: groupID,
MemberID: memberID, MemberID: memberID,
GroupInstanceID: "", // Simplified - would parse from remaining data
GroupInstanceID: "", // Simplified - would parse from remaining data
Members: []LeaveGroupMember{}, // Would parse members array for batch operations Members: []LeaveGroupMember{}, // Would parse members array for batch operations
}, nil }, nil
} }
func (h *Handler) buildHeartbeatResponse(response HeartbeatResponse) []byte { func (h *Handler) buildHeartbeatResponse(response HeartbeatResponse) []byte {
result := make([]byte, 0, 12) result := make([]byte, 0, 12)
// Correlation ID (4 bytes) // Correlation ID (4 bytes)
correlationIDBytes := make([]byte, 4) correlationIDBytes := make([]byte, 4)
binary.BigEndian.PutUint32(correlationIDBytes, response.CorrelationID) binary.BigEndian.PutUint32(correlationIDBytes, response.CorrelationID)
result = append(result, correlationIDBytes...) result = append(result, correlationIDBytes...)
// Error code (2 bytes) // Error code (2 bytes)
errorCodeBytes := make([]byte, 2) errorCodeBytes := make([]byte, 2)
binary.BigEndian.PutUint16(errorCodeBytes, uint16(response.ErrorCode)) binary.BigEndian.PutUint16(errorCodeBytes, uint16(response.ErrorCode))
result = append(result, errorCodeBytes...) result = append(result, errorCodeBytes...)
// Throttle time (4 bytes, 0 = no throttling) // Throttle time (4 bytes, 0 = no throttling)
result = append(result, 0, 0, 0, 0) result = append(result, 0, 0, 0, 0)
return result return result
} }
@ -307,53 +307,53 @@ func (h *Handler) buildLeaveGroupResponse(response LeaveGroupResponse) []byte {
for _, member := range response.Members { for _, member := range response.Members {
estimatedSize += len(member.MemberID) + len(member.GroupInstanceID) + 8 estimatedSize += len(member.MemberID) + len(member.GroupInstanceID) + 8
} }
result := make([]byte, 0, estimatedSize) result := make([]byte, 0, estimatedSize)
// Correlation ID (4 bytes) // Correlation ID (4 bytes)
correlationIDBytes := make([]byte, 4) correlationIDBytes := make([]byte, 4)
binary.BigEndian.PutUint32(correlationIDBytes, response.CorrelationID) binary.BigEndian.PutUint32(correlationIDBytes, response.CorrelationID)
result = append(result, correlationIDBytes...) result = append(result, correlationIDBytes...)
// Error code (2 bytes) // Error code (2 bytes)
errorCodeBytes := make([]byte, 2) errorCodeBytes := make([]byte, 2)
binary.BigEndian.PutUint16(errorCodeBytes, uint16(response.ErrorCode)) binary.BigEndian.PutUint16(errorCodeBytes, uint16(response.ErrorCode))
result = append(result, errorCodeBytes...) result = append(result, errorCodeBytes...)
// Members array length (4 bytes) // Members array length (4 bytes)
membersLengthBytes := make([]byte, 4) membersLengthBytes := make([]byte, 4)
binary.BigEndian.PutUint32(membersLengthBytes, uint32(len(response.Members))) binary.BigEndian.PutUint32(membersLengthBytes, uint32(len(response.Members)))
result = append(result, membersLengthBytes...) result = append(result, membersLengthBytes...)
// Members // Members
for _, member := range response.Members { for _, member := range response.Members {
// Member ID length (2 bytes) // Member ID length (2 bytes)
memberIDLength := make([]byte, 2) memberIDLength := make([]byte, 2)
binary.BigEndian.PutUint16(memberIDLength, uint16(len(member.MemberID))) binary.BigEndian.PutUint16(memberIDLength, uint16(len(member.MemberID)))
result = append(result, memberIDLength...) result = append(result, memberIDLength...)
// Member ID // Member ID
result = append(result, []byte(member.MemberID)...) result = append(result, []byte(member.MemberID)...)
// Group instance ID length (2 bytes) // Group instance ID length (2 bytes)
instanceIDLength := make([]byte, 2) instanceIDLength := make([]byte, 2)
binary.BigEndian.PutUint16(instanceIDLength, uint16(len(member.GroupInstanceID))) binary.BigEndian.PutUint16(instanceIDLength, uint16(len(member.GroupInstanceID)))
result = append(result, instanceIDLength...) result = append(result, instanceIDLength...)
// Group instance ID // Group instance ID
if len(member.GroupInstanceID) > 0 { if len(member.GroupInstanceID) > 0 {
result = append(result, []byte(member.GroupInstanceID)...) result = append(result, []byte(member.GroupInstanceID)...)
} }
// Error code (2 bytes) // Error code (2 bytes)
memberErrorBytes := make([]byte, 2) memberErrorBytes := make([]byte, 2)
binary.BigEndian.PutUint16(memberErrorBytes, uint16(member.ErrorCode)) binary.BigEndian.PutUint16(memberErrorBytes, uint16(member.ErrorCode))
result = append(result, memberErrorBytes...) result = append(result, memberErrorBytes...)
} }
// Throttle time (4 bytes, 0 = no throttling) // Throttle time (4 bytes, 0 = no throttling)
result = append(result, 0, 0, 0, 0) result = append(result, 0, 0, 0, 0)
return result return result
} }
@ -362,7 +362,7 @@ func (h *Handler) buildHeartbeatErrorResponse(correlationID uint32, errorCode in
CorrelationID: correlationID, CorrelationID: correlationID,
ErrorCode: errorCode, ErrorCode: errorCode,
} }
return h.buildHeartbeatResponse(response) return h.buildHeartbeatResponse(response)
} }
@ -372,7 +372,7 @@ func (h *Handler) buildLeaveGroupErrorResponse(correlationID uint32, errorCode i
ErrorCode: errorCode, ErrorCode: errorCode,
Members: []LeaveGroupMemberResponse{}, Members: []LeaveGroupMemberResponse{},
} }
return h.buildLeaveGroupResponse(response) return h.buildLeaveGroupResponse(response)
} }

13
weed/mq/kafka/protocol/fetch.go

@ -344,8 +344,9 @@ func (h *Handler) constructRecordBatchFromLedger(ledger interface{}, fetchOffset
batch = append(batch, 0, 0, 0, 0) // partition leader epoch (4 bytes) batch = append(batch, 0, 0, 0, 0) // partition leader epoch (4 bytes)
batch = append(batch, 2) // magic byte (version 2) (1 byte) batch = append(batch, 2) // magic byte (version 2) (1 byte)
// CRC placeholder (4 bytes) - for testing, use 0
batch = append(batch, 0, 0, 0, 0) // CRC32
// CRC placeholder (4 bytes) - will be calculated at the end
crcPos := len(batch)
batch = append(batch, 0, 0, 0, 0) // CRC32 placeholder
// Batch attributes (2 bytes) - no compression, no transactional // Batch attributes (2 bytes) - no compression, no transactional
batch = append(batch, 0, 0) // attributes batch = append(batch, 0, 0) // attributes
@ -449,6 +450,14 @@ func (h *Handler) constructRecordBatchFromLedger(ledger interface{}, fetchOffset
batchLength := uint32(len(batch) - batchLengthPos - 4) batchLength := uint32(len(batch) - batchLengthPos - 4)
binary.BigEndian.PutUint32(batch[batchLengthPos:batchLengthPos+4], batchLength) binary.BigEndian.PutUint32(batch[batchLengthPos:batchLengthPos+4], batchLength)
// Calculate CRC32 for the batch
// CRC is calculated over: attributes + last_offset_delta + first_timestamp + max_timestamp + producer_id + producer_epoch + base_sequence + records_count + records
// This starts after the CRC field (which comes after magic byte)
crcStartPos := crcPos + 4 // start after the CRC field
crcData := batch[crcStartPos:]
crc := crc32.ChecksumIEEE(crcData)
binary.BigEndian.PutUint32(batch[crcPos:crcPos+4], crc)
return batch return batch
} }

Loading…
Cancel
Save