feat: add kafka admin integration
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/segmentio/kafka-go"
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store"
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
const (
|
||||
configEventTenantUpdated = "tenant.updated"
|
||||
configEventTenantStatusChanged = "tenant.status.changed"
|
||||
configEventTenantLimitsUpdated = "tenant.limits.updated"
|
||||
configEventTenantFeaturesUpdated = "tenant.features.updated"
|
||||
configEventDictionaryUpdated = "dictionary.updated"
|
||||
configEventThirdPartyUpdated = "third_party.updated"
|
||||
configEventRegistrationUpdated = "registration.config.updated"
|
||||
)
|
||||
|
||||
type configEventPayload struct {
|
||||
TenantCode string `json:"tenant_code"`
|
||||
TenantID types.TenantID `json:"tenant_id"`
|
||||
Group string `json:"group"`
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
func startKafkaConfigEventConsumer() {
|
||||
startKafkaConsumer("config_event", handleKafkaConfigEvent)
|
||||
}
|
||||
|
||||
func handleKafkaConfigEvent(ctx context.Context, envelope kafkaEnvelope, _ kafka.Message) error {
|
||||
if strings.TrimSpace(envelope.EventID) == "" {
|
||||
return nil
|
||||
}
|
||||
tenantID, _ := kafkaTenantIDFromEnvelopeOrPayload(envelope)
|
||||
if tenantID.IsZero() {
|
||||
tenantID = types.TenantID(1)
|
||||
}
|
||||
consume, claimed, err := beginKafkaConsumeForTopic(tenantID, envelope, "config_event")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !claimed {
|
||||
logs.Info.Printf("kafka config event skipped: event=%s group=%s status=%s", envelope.EventID, kafkaBus.config.ConsumerGroup, consume.Status)
|
||||
return nil
|
||||
}
|
||||
if err = executeKafkaConfigEvent(envelope); err != nil {
|
||||
reason := kafkaCommandErrorReason(err)
|
||||
failed, markErr := store.HTTPInfrastructure.MarkKafkaConsumeFailed(tenantID, envelope.EventID, kafkaBus.config.ConsumerGroup, reason, err.Error(), time.Now().UTC().Round(time.Millisecond))
|
||||
if markErr != nil {
|
||||
return markErr
|
||||
}
|
||||
if failed.RetryCount >= kafkaBus.config.MaxRetryCount {
|
||||
return markKafkaDeadLetter(ctx, tenantID, envelope, failed, reason, err.Error())
|
||||
}
|
||||
sleepKafkaRetryBackoff(failed.RetryCount)
|
||||
return err
|
||||
}
|
||||
return store.HTTPInfrastructure.MarkKafkaConsumeSucceeded(tenantID, envelope.EventID, kafkaBus.config.ConsumerGroup, time.Now().UTC().Round(time.Millisecond))
|
||||
}
|
||||
|
||||
func executeKafkaConfigEvent(envelope kafkaEnvelope) error {
|
||||
var payload configEventPayload
|
||||
if len(envelope.Payload) > 0 {
|
||||
if err := json.Unmarshal(envelope.Payload, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tenantCode := firstNonEmpty(payload.TenantCode, envelope.TenantCode)
|
||||
tenantID := payload.TenantID
|
||||
if tenantID.IsZero() && tenantCode != "" {
|
||||
if tenant, err := store.Tenants.GetByCode(tenantCode); err == nil && tenant != nil {
|
||||
tenantID = tenant.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(envelope.EventType) {
|
||||
case configEventTenantUpdated, configEventTenantStatusChanged, configEventTenantLimitsUpdated, configEventTenantFeaturesUpdated:
|
||||
store.InvalidateTenantConfigCache(tenantCode, tenantID)
|
||||
case configEventDictionaryUpdated, configEventThirdPartyUpdated:
|
||||
store.InvalidateThirdPartyConfigCache(payload.Group)
|
||||
case configEventRegistrationUpdated:
|
||||
store.InvalidateRegistrationConfigCache()
|
||||
default:
|
||||
store.InvalidateTenantConfigCache(tenantCode, tenantID)
|
||||
store.InvalidateThirdPartyConfigCache(payload.Group)
|
||||
store.InvalidateRegistrationConfigCache()
|
||||
}
|
||||
logs.Info.Printf("kafka config event applied: event=%s type=%s tenant=%s group=%s", envelope.EventID, envelope.EventType, tenantCode, payload.Group)
|
||||
return nil
|
||||
}
|
||||
|
||||
func kafkaTenantIDFromEnvelopeOrPayload(envelope kafkaEnvelope) (types.TenantID, error) {
|
||||
var payload configEventPayload
|
||||
if len(envelope.Payload) > 0 {
|
||||
_ = json.Unmarshal(envelope.Payload, &payload)
|
||||
}
|
||||
if payload.TenantID.IsValid() {
|
||||
return payload.TenantID, nil
|
||||
}
|
||||
return kafkaTenantID(firstNonEmpty(envelope.TenantCode, payload.TenantCode))
|
||||
}
|
||||
|
||||
func beginKafkaConsumeForTopic(tenantID types.TenantID, envelope kafkaEnvelope, topicName string) (*types.KafkaConsumeRecord, bool, error) {
|
||||
if kafkaBus == nil {
|
||||
return nil, false, types.ErrUnsupported
|
||||
}
|
||||
deadline := time.Now().UTC().Add(5 * time.Minute).Round(time.Millisecond)
|
||||
return store.HTTPInfrastructure.BeginKafkaConsume(tenantID, &types.KafkaConsumeRecord{
|
||||
EventID: envelope.EventID,
|
||||
ConsumerGroup: kafkaBus.config.ConsumerGroup,
|
||||
SourceTopic: kafkaTopic(topicName),
|
||||
EventType: envelope.EventType,
|
||||
BizType: envelope.BizType,
|
||||
BizID: envelope.BizID,
|
||||
}, deadline)
|
||||
}
|
||||
Reference in New Issue
Block a user