improvements jan2025 (#6)

* feat(controller): add lazy watcher, improve resource usage and add pattern validation

- [x] Add cache sync health check for readiness probe verification
- [x] Create namespace lister with API reader support for fresh label queries
- [x] Add pattern validation with warning logs for invalid glob patterns
- [x] Implement lazy watcher initialization mode to scan for active resources
- [x] Add requeue delay to namespace reconciler for cache settlement
- [x] Replace custom containsString with slices.Contains from stdlib
- [x] Add structured logging context to reconcilers (kind, group, version)
- [x] Improve error variable naming for clarity in nested conditions
- [x] Add nil-safe label access in namespace reconciler setup
- [x] Add APIReader to namespace and source reconcilers for fresh data
- [x] Improve type assertions with proper error handling in mirror operations
- [x] Reorder struct fields for consistency and readability
- [x] Add comprehensive pattern validation tests and validation API

* feat(controller): add lazy watcher, improve resource usage and add pattern validation

- [x] Add circuit breaker for reconciliation failure tracking and prevention
- [x] Implement granular registration state tracking (not-registered, source-only, fully-registered)
- [x] Add lazy controller initialization for active resource types only
- [x] Consolidate namespace listing into single API call for efficiency
- [x] Add mirror creation verification to catch webhook rejections
- [x] Implement high-cardinality resource detection and warnings
- [x] Add source deletion check in mirror reconciler to prevent races
- [x] Preserve transformation annotations on errors in mirror reconciliation
- [x] Expand constants documentation with labels vs annotations design rationale
- [x] Add comprehensive test coverage for circuit breaker and registration states
- [x] Add mutation-safety tests for hash computation

* fixup! feat(controller): add lazy watcher, improve resource usage and add pattern validation
This commit is contained in:
2026-01-14 13:07:11 +00:00
committed by GitHub
parent 4f8e2783cf
commit 096dca47d1
22 changed files with 1937 additions and 266 deletions
+69 -32
View File
@@ -4,6 +4,8 @@ package controller
import (
"context"
"fmt"
"slices"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
@@ -20,21 +22,31 @@ import (
"github.com/lukaszraczylo/kubemirror/pkg/filter"
)
const (
// cacheSettleDelay is the time to wait after namespace label changes
// to allow informer caches to sync. This addresses the race condition
// where namespace watch events fire before the cache is updated.
cacheSettleDelay = 3 * time.Second
)
// NamespaceReconciler watches for namespace CREATE and UPDATE events
// and triggers reconciliation of source resources that match the new namespace.
type NamespaceReconciler struct {
client.Client
NamespaceLister NamespaceLister
APIReader client.Reader
Scheme *runtime.Scheme
Config *config.Config
Filter *filter.NamespaceFilter
NamespaceLister NamespaceLister
// ResourceTypes contains all discovered resource types to reconcile
ResourceTypes []config.ResourceType
ResourceTypes []config.ResourceType
}
// Reconcile processes namespace events and creates mirrors for matching sources.
func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx).WithValues("namespace", req.Name)
logger := log.FromContext(ctx).WithValues(
"namespace", req.Name,
"reconciler", "namespace",
)
// Fetch the namespace
namespace := &corev1.Namespace{}
@@ -76,7 +88,11 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return ctrl.Result{}, fmt.Errorf("failed to reconcile %d source resources", totalErrors)
}
return ctrl.Result{}, nil
// Requeue with delay to catch any updates missed due to cache staleness.
// This is particularly important for namespace label changes where the
// informer cache may not yet reflect the new label state. The delay allows
// the cache to settle and ensures all relevant source resources are reconciled.
return ctrl.Result{RequeueAfter: cacheSettleDelay}, nil
}
// reconcileResourceType finds and reconciles all sources of a specific resource type
@@ -125,13 +141,7 @@ func (r *NamespaceReconciler) reconcileResourceType(ctx context.Context, rt conf
}
// Check if the new namespace matches this source's targets
var isTarget bool
for _, target := range targetNamespaces {
if target == namespaceName {
isTarget = true
break
}
}
isTarget := slices.Contains(targetNamespaces, namespaceName)
if isTarget {
// Create or update mirror in the namespace
@@ -222,30 +232,47 @@ func (r *NamespaceReconciler) resolveTargetNamespaces(ctx context.Context, sourc
return nil, nil
}
// Get all namespaces
allNamespaces, err := r.NamespaceLister.ListNamespaces(ctx)
// Validate patterns and log warnings for invalid ones
validationResults, allValid := filter.ValidatePatterns(patterns)
if !allValid {
logger := log.FromContext(ctx)
invalidPatterns := filter.InvalidPatterns(validationResults)
for _, invalid := range invalidPatterns {
logger.Info("invalid glob pattern in target-namespaces annotation, pattern will be skipped",
"pattern", invalid.Pattern,
"error", invalid.Error.Error(),
"source", source.GetName(),
"namespace", source.GetNamespace(),
)
}
// Filter to only valid patterns
var validPatterns []string
for _, result := range validationResults {
if result.Valid {
validPatterns = append(validPatterns, result.Pattern)
}
}
patterns = validPatterns
// If no valid patterns remain, return empty
if len(patterns) == 0 {
return nil, nil
}
}
// Get all namespace info in a single API call (more efficient than 3 separate calls)
nsInfo, err := r.NamespaceLister.ListNamespacesWithLabels(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list namespaces: %w", err)
}
// Get namespaces with allow-mirrors label
allowMirrorsNamespaces, err := r.NamespaceLister.ListAllowMirrorsNamespaces(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list allow-mirrors namespaces: %w", err)
}
// Get namespaces that have explicitly opted out (allow-mirrors="false")
optOutNamespaces, err := r.NamespaceLister.ListOptOutNamespaces(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list opt-out namespaces: %w", err)
}
// Resolve target namespaces
// Resolve target namespaces using the pre-categorized namespace info
targetNamespaces := filter.ResolveTargetNamespaces(
patterns,
allNamespaces,
allowMirrorsNamespaces,
optOutNamespaces,
nsInfo.All,
nsInfo.AllowMirrors,
nsInfo.OptOut,
source.GetNamespace(),
r.Filter,
)
@@ -292,8 +319,18 @@ func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
// Check if allow-mirrors label changed
oldLabel := oldNs.Labels[constants.LabelAllowMirrors]
newLabel := newNs.Labels[constants.LabelAllowMirrors]
// Use GetLabels() to safely handle nil labels map
oldLabels := oldNs.GetLabels()
newLabels := newNs.GetLabels()
// Get label values with nil-safe access
var oldLabel, newLabel string
if oldLabels != nil {
oldLabel = oldLabels[constants.LabelAllowMirrors]
}
if newLabels != nil {
newLabel = newLabels[constants.LabelAllowMirrors]
}
return oldLabel != newLabel
},