main.go called Reconcile(ctx, req.Name) without req.Namespace, so the Get against the namespaced Queue CRD always 404'd and was silently swallowed as success -- no shard topics or Redis state were ever created. Separately, ShardStatus.HashRangeStart/End were uint32, but controller-gen maps that to OpenAPI format:int32, whose max (2147483647) is smaller than FullHashRangeEnd (0xFFFFFFFF), so the apiserver rejected every status update with the (misleadingly empty-looking) "must be of type integer with format int32" error. Widened to int64, regenerated the CRD, and synced the chart's bundled copy.
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
// Command queue-operator runs the Queue CRD reconciler described in
|
|
// design.md §2a/§2c (shard-aware control plane: creates shard topics,
|
|
// performs capacity-driven shard splits, drains closed shards, and
|
|
// publishes queue metadata + the shard map to Redis for the message plane).
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
goredis "github.com/redis/go-redis/v9"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
|
ctrl "sigs.k8s.io/controller-runtime"
|
|
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
kmsvcv1 "github.com/rockliang/kafka-management-service/apis/kmsvc/v1"
|
|
"github.com/rockliang/kafka-management-service/internal/kafka"
|
|
"github.com/rockliang/kafka-management-service/internal/operator"
|
|
)
|
|
|
|
func main() {
|
|
ctrl.SetLogger(zap.New())
|
|
|
|
brokers := splitCSV(getEnv("KMSVC_KAFKA_BROKERS", "localhost:9092"))
|
|
redisAddr := getEnv("KMSVC_REDIS_ADDR", "localhost:6379")
|
|
redisPassword := os.Getenv("KMSVC_REDIS_PASSWORD")
|
|
redisDB := 0
|
|
if v := os.Getenv("KMSVC_REDIS_DB"); v != "" {
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
exitf("invalid KMSVC_REDIS_DB: %v", err)
|
|
}
|
|
redisDB = n
|
|
}
|
|
|
|
scheme := runtime.NewScheme()
|
|
if err := clientgoscheme.AddToScheme(scheme); err != nil {
|
|
exitf("registering client-go scheme: %v", err)
|
|
}
|
|
if err := kmsvcv1.AddToScheme(scheme); err != nil {
|
|
exitf("registering kmsvc scheme: %v", err)
|
|
}
|
|
|
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{Scheme: scheme})
|
|
if err != nil {
|
|
exitf("creating manager: %v", err)
|
|
}
|
|
|
|
admin, err := kafka.NewAdmin(brokers)
|
|
if err != nil {
|
|
exitf("creating kafka admin: %v", err)
|
|
}
|
|
|
|
rdb := goredis.NewClient(&goredis.Options{Addr: redisAddr, Password: redisPassword, DB: redisDB})
|
|
if err := rdb.Ping(context.Background()).Err(); err != nil {
|
|
exitf("connecting to redis at %s: %v", redisAddr, err)
|
|
}
|
|
|
|
reconciler := &operator.QueueReconciler{
|
|
Client: mgr.GetClient(),
|
|
Admin: admin,
|
|
Redis: rdb,
|
|
Now: time.Now,
|
|
}
|
|
|
|
err = ctrl.NewControllerManagedBy(mgr).
|
|
For(&kmsvcv1.Queue{}).
|
|
Complete(reconcile.Func(func(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
|
|
if err := reconciler.Reconcile(ctx, req.Namespace, req.Name); err != nil {
|
|
return reconcile.Result{}, err
|
|
}
|
|
return reconcile.Result{}, nil
|
|
}))
|
|
if err != nil {
|
|
exitf("setting up controller: %v", err)
|
|
}
|
|
|
|
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
|
exitf("running manager: %v", err)
|
|
}
|
|
}
|
|
|
|
func getEnv(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func splitCSV(s string) []string {
|
|
parts := strings.Split(s, ",")
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func exitf(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
|
os.Exit(1)
|
|
}
|