Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
035de79
Remove CEL expression causing controller failures
rytswd Nov 21, 2025
5c97682
Update to use the latest API spec
rytswd Nov 21, 2025
f927e2d
Add TopoServer handling controller
rytswd Nov 21, 2025
aa12d8d
Add Cell CR handling controller
rytswd Nov 21, 2025
fc3ab67
Add Shard CR handling controller
rytswd Nov 21, 2025
8d7e524
Merge remote-tracking branch 'origin/main' into update-with-latest-crds
rytswd Nov 22, 2025
ecaf4ac
Adjust label handling to default to use k8s only
rytswd Nov 22, 2025
602f15a
Adjust test cases for label management
rytswd Nov 22, 2025
c9c482e
Tidy import
rytswd Nov 22, 2025
82274ea
Merge remote-tracking branch 'origin/main' into update-with-latest-crds
rytswd Nov 22, 2025
c0b1a90
Update to pull in latest MultiOrch API changes
rytswd Nov 22, 2025
090105e
Remove the default multigres.com/cell label
rytswd Nov 22, 2025
345aae9
Adjust replica counting to be based on Cell count
rytswd Nov 22, 2025
e25039b
Merge remote-tracking branch 'origin/main' into update-with-latest-crds
rytswd Nov 22, 2025
aa9640a
Simplify pool name generation
rytswd Nov 22, 2025
85c1897
Update API reference with the latest spec
rytswd Nov 22, 2025
f732bff
Fix up based on pools definition using map
rytswd Nov 22, 2025
0a2046f
Add container arg handling
rytswd Nov 22, 2025
1801075
Fix up naming convention based on pool definitions
rytswd Nov 22, 2025
0edef5e
Ensure no race condition occurs with multiple runs
rytswd Nov 24, 2025
d378af8
Remove unnecessary test scheme binding
rytswd Nov 24, 2025
917a459
Fix race condition caused by parallel pod use
rytswd Nov 24, 2025
fbf6c92
Adjust naming and indentation
rytswd Nov 24, 2025
7391c59
Use simplified pool name
rytswd Nov 24, 2025
c3e7430
Adjust pool name test to match the impl
rytswd Nov 24, 2025
d47fa13
Update statefulset name handling to match service
rytswd Nov 24, 2025
f370cfc
Update label testing with comprehensive list
rytswd Nov 24, 2025
9d3623c
Adjust slice to map based on pools API change
rytswd Nov 24, 2025
14f119c
Correct with go-cmp comparison for status
rytswd Nov 24, 2025
48c7f24
Add edge case testing
rytswd Nov 24, 2025
8691ec5
Use blackbox test by using separate package name
rytswd Nov 24, 2025
f073423
Ensure test cases to match latest shard spec
rytswd Nov 24, 2025
a43e601
Ensure to add command arguments
rytswd Nov 24, 2025
202b33f
Remove old implementation to avoid build error
rytswd Nov 24, 2025
6f52772
Add clarification comment
rytswd Nov 24, 2025
7dec490
Run make lint-fix
rytswd Nov 24, 2025
a3bd4e0
Fix IgnoreStatus handling
rytswd Nov 24, 2025
183143e
Remove old MultiOrch deployment setup from cell
rytswd Nov 26, 2025
5b85301
Add comment about the migration from cell to shard
rytswd Nov 26, 2025
37a8824
Add argument and port details for MultiOrch
rytswd Nov 26, 2025
16bbcb6
Add more test for testutil
rytswd Nov 26, 2025
5bb387c
Add helper function for field name compare
rytswd Nov 30, 2025
89503d2
Ensure compare test cases
rytswd Nov 30, 2025
9e6cd75
Add extra test setup for envtest edge cases
rytswd Dec 3, 2025
0b8d7ed
Correct event type watch to be consistent
rytswd Dec 3, 2025
a4aba21
Add more test cases for resource watcher
rytswd Dec 3, 2025
5ee7061
Simplify integration test setup
rytswd Dec 4, 2025
cf01ed4
Add excessive test setup
rytswd Dec 8, 2025
2b8f39f
Split resource watcher into multiple files
rytswd Dec 8, 2025
fffc031
Remove hack and update with interface
rytswd Dec 9, 2025
0fe1632
Move mockTB
rytswd Dec 9, 2025
9ea4a9b
Run lint
rytswd Dec 9, 2025
a69d1f5
Remove unnecessary code, add verbose flag to make
rytswd Dec 9, 2025
fddf87e
Temporarily add cleanup for internal test
rytswd Dec 9, 2025
6fcdf2d
Make sure integraiton build flag added to test
rytswd Dec 12, 2025
3d65b5e
Reformat for better visibility
rytswd Dec 12, 2025
80ac47e
Combine tests using table driven definitions
rytswd Dec 12, 2025
e785ea2
Merge remote-tracking branch 'origin/main' into update-with-latest-crds
rytswd Dec 12, 2025
2f63908
Use testutil from dedicated Go module
rytswd Dec 12, 2025
fc7b541
Remove testutil from resource-handler
rytswd Dec 12, 2025
64cfd82
Remove old reference of testutil
rytswd Dec 13, 2025
e9c3f5f
Ensure integration test uses custom CRDs
rytswd Dec 13, 2025
9388d20
Move pool label definitions to labels.go
rytswd Dec 13, 2025
076c82c
Add TODO comment
rytswd Dec 13, 2025
ec03d13
Remove unnecessary kubebuilder markers
rytswd Dec 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
252 changes: 252 additions & 0 deletions pkg/resource-handler/controller/cell/cell_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
package cell

import (
"context"
"fmt"
"slices"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/log"

multigresv1alpha1 "github.com/numtide/multigres-operator/api/v1alpha1"
)

const (
finalizerName = "cell.multigres.com/finalizer"
)

// CellReconciler reconciles a Cell object.
type CellReconciler struct {
client.Client
Scheme *runtime.Scheme
}

// Reconcile handles Cell resource reconciliation.
func (r *CellReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)

// Fetch the Cell instance
cell := &multigresv1alpha1.Cell{}
if err := r.Get(ctx, req.NamespacedName, cell); err != nil {
if errors.IsNotFound(err) {
logger.Info("Cell resource not found, ignoring")
return ctrl.Result{}, nil
}
logger.Error(err, "Failed to get Cell")
return ctrl.Result{}, err
}

// Handle deletion
if !cell.DeletionTimestamp.IsZero() {
return r.handleDeletion(ctx, cell)
}

// Add finalizer if not present
if !slices.Contains(cell.Finalizers, finalizerName) {
cell.Finalizers = append(cell.Finalizers, finalizerName)
if err := r.Update(ctx, cell); err != nil {
logger.Error(err, "Failed to add finalizer")
return ctrl.Result{}, err
}
}

// Reconcile MultiGateway Deployment
if err := r.reconcileMultiGatewayDeployment(ctx, cell); err != nil {
logger.Error(err, "Failed to reconcile MultiGateway Deployment")
return ctrl.Result{}, err
}

// Reconcile MultiGateway Service
if err := r.reconcileMultiGatewayService(ctx, cell); err != nil {
logger.Error(err, "Failed to reconcile MultiGateway Service")
return ctrl.Result{}, err
}

// Update status
if err := r.updateStatus(ctx, cell); err != nil {
logger.Error(err, "Failed to update status")
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// handleDeletion handles cleanup when Cell is being deleted.
func (r *CellReconciler) handleDeletion(
ctx context.Context,
cell *multigresv1alpha1.Cell,
) (ctrl.Result, error) {
logger := log.FromContext(ctx)

if slices.Contains(cell.Finalizers, finalizerName) {
// Perform cleanup if needed
// Currently no special cleanup required - owner references handle resource deletion

// Remove finalizer
cell.Finalizers = slices.DeleteFunc(cell.Finalizers, func(s string) bool {
return s == finalizerName
})
if err := r.Update(ctx, cell); err != nil {
logger.Error(err, "Failed to remove finalizer")
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

// reconcileMultiGatewayDeployment creates or updates the MultiGateway Deployment.
func (r *CellReconciler) reconcileMultiGatewayDeployment(
ctx context.Context,
cell *multigresv1alpha1.Cell,
) error {
desired, err := BuildMultiGatewayDeployment(cell, r.Scheme)
if err != nil {
return fmt.Errorf("failed to build MultiGateway Deployment: %w", err)
}

existing := &appsv1.Deployment{}
name := cell.Name + "-multigateway"
err = r.Get(ctx, client.ObjectKey{Namespace: cell.Namespace, Name: name}, existing)
if err != nil {
if errors.IsNotFound(err) {
// Create new Deployment
if err := r.Create(ctx, desired); err != nil {
return fmt.Errorf("failed to create MultiGateway Deployment: %w", err)
}
return nil
}
return fmt.Errorf("failed to get MultiGateway Deployment: %w", err)
}

// Update existing Deployment
existing.Spec = desired.Spec
existing.Labels = desired.Labels
if err := r.Update(ctx, existing); err != nil {
return fmt.Errorf("failed to update MultiGateway Deployment: %w", err)
}

return nil
}

// reconcileMultiGatewayService creates or updates the MultiGateway Service.
func (r *CellReconciler) reconcileMultiGatewayService(
ctx context.Context,
cell *multigresv1alpha1.Cell,
) error {
desired, err := BuildMultiGatewayService(cell, r.Scheme)
if err != nil {
return fmt.Errorf("failed to build MultiGateway Service: %w", err)
}

existing := &corev1.Service{}
name := cell.Name + "-multigateway"
err = r.Get(ctx, client.ObjectKey{Namespace: cell.Namespace, Name: name}, existing)
if err != nil {
if errors.IsNotFound(err) {
// Create new Service
if err := r.Create(ctx, desired); err != nil {
return fmt.Errorf("failed to create MultiGateway Service: %w", err)
}
return nil
}
return fmt.Errorf("failed to get MultiGateway Service: %w", err)
}

// Update existing Service
existing.Spec.Ports = desired.Spec.Ports
existing.Spec.Selector = desired.Spec.Selector
existing.Labels = desired.Labels
if err := r.Update(ctx, existing); err != nil {
return fmt.Errorf("failed to update MultiGateway Service: %w", err)
}

return nil
}

// updateStatus updates the Cell status based on observed state.
func (r *CellReconciler) updateStatus(ctx context.Context, cell *multigresv1alpha1.Cell) error {
// Get the MultiGateway Deployment to check status
mgDeploy := &appsv1.Deployment{}
err := r.Get(
ctx,
client.ObjectKey{Namespace: cell.Namespace, Name: cell.Name + "-multigateway"},
mgDeploy,
)
if err != nil {
if errors.IsNotFound(err) {
// Deployment not created yet
return nil
}
return fmt.Errorf("failed to get MultiGateway Deployment for status: %w", err)
}

// Update status fields
cell.Status.ObservedGeneration = cell.Generation

// Update conditions
cell.Status.Conditions = r.buildConditions(cell, mgDeploy)

if err := r.Status().Update(ctx, cell); err != nil {
return fmt.Errorf("failed to update status: %w", err)
}

return nil
}

// buildConditions creates status conditions based on observed state.
func (r *CellReconciler) buildConditions(
cell *multigresv1alpha1.Cell,
mgDeploy *appsv1.Deployment,
) []metav1.Condition {
conditions := []metav1.Condition{}

// Ready condition - MultiGateway must be ready
readyCondition := metav1.Condition{
Type: "Ready",
ObservedGeneration: cell.Generation,
LastTransitionTime: metav1.Now(),
}

mgReady := mgDeploy.Status.ReadyReplicas == mgDeploy.Status.Replicas &&
mgDeploy.Status.Replicas > 0

if mgReady {
readyCondition.Status = metav1.ConditionTrue
readyCondition.Reason = "MultiGatewayReady"
readyCondition.Message = fmt.Sprintf(
"MultiGateway %d/%d ready",
mgDeploy.Status.ReadyReplicas,
mgDeploy.Status.Replicas,
)
} else {
readyCondition.Status = metav1.ConditionFalse
readyCondition.Reason = "MultiGatewayNotReady"
readyCondition.Message = fmt.Sprintf("MultiGateway %d/%d ready", mgDeploy.Status.ReadyReplicas, mgDeploy.Status.Replicas)
}

conditions = append(conditions, readyCondition)
return conditions
}

// SetupWithManager sets up the controller with the Manager.
func (r *CellReconciler) SetupWithManager(mgr ctrl.Manager, opts ...controller.Options) error {
controllerOpts := controller.Options{}
if len(opts) > 0 {
controllerOpts = opts[0]
}

return ctrl.NewControllerManagedBy(mgr).
For(&multigresv1alpha1.Cell{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
WithOptions(controllerOpts).
Complete(r)
}
Loading