Skip to content

Commit 553af62

Browse files
authored
Merge pull request #120 from datum-cloud/fix/mgmt-controller-fail-loud
fix(mgmt): fail loud on missing federation kubeconfig; rename federation client
2 parents c1c6261 + 6ae41d4 commit 553af62

9 files changed

Lines changed: 132 additions & 106 deletions

File tree

cmd/main.go

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ var (
6666
gitTreeState = "unknown"
6767
buildDate = "unknown"
6868

69-
// upstreamRestConfig holds the REST config for the upstream federation control
70-
// plane (Karmada). It is populated from --upstream-kubeconfig when set, and
71-
// is nil when the flag is omitted.
72-
upstreamRestConfig *rest.Config
69+
// federationRestConfig holds the REST config for the Karmada federation control
70+
// plane. It is populated from --federation-kubeconfig when set, and is nil
71+
// when the flag is omitted.
72+
federationRestConfig *rest.Config
7373
)
7474

7575
func init() {
@@ -86,14 +86,15 @@ func init() {
8686
// +kubebuilder:scaffold:scheme
8787
}
8888

89+
//nolint:gocyclo // main wires all controller paths; complexity is inherent to startup sequencing
8990
func main() {
9091

9192
var enableLeaderElection bool
9293
var leaderElectionNamespace string
9394
var probeAddr string
9495
var serverConfigFile string
95-
var upstreamKubeconfig string
96-
var upstreamContext string
96+
var federationKubeconfig string
97+
var federationContext string
9798
var enableManagementControllers bool
9899
var enableCellControllers bool
99100

@@ -102,11 +103,12 @@ func main() {
102103
"Enable leader election for controller manager. "+
103104
"Enabling this will ensure there is only one active controller manager.")
104105
flag.StringVar(&leaderElectionNamespace, "leader-elect-namespace", "", "The namespace to use for leader election.")
105-
flag.StringVar(&upstreamKubeconfig, "upstream-kubeconfig", "",
106-
"Path to the kubeconfig file for the upstream federation control plane (Karmada). "+
106+
flag.StringVar(&federationKubeconfig, "federation-kubeconfig", "",
107+
"Path to the kubeconfig file for the Karmada federation control plane. "+
108+
"Required when --enable-management-controllers is set. "+
107109
"When omitted, federation features are disabled.")
108-
flag.StringVar(&upstreamContext, "upstream-context", "",
109-
"Context to use from the upstream kubeconfig. When omitted, the current context is used.")
110+
flag.StringVar(&federationContext, "federation-context", "",
111+
"Context to use from the federation kubeconfig. When omitted, the current context is used.")
110112
flag.BoolVar(&enableManagementControllers, "enable-management-controllers", false,
111113
"Enable management-plane controllers (WorkloadDeploymentFederator, InstanceProjector).")
112114
flag.BoolVar(&enableCellControllers, "enable-cell-controllers", false,
@@ -123,21 +125,35 @@ func main() {
123125

124126
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
125127

126-
// Load the downstream REST config when --downstream-kubeconfig is provided.
127-
// When the flag is omitted, upstreamRestConfig remains nil and federation
128-
// features will be skipped at controller setup time.
129-
if upstreamKubeconfig != "" {
128+
// Load the federation (Karmada) control plane REST config when
129+
// --federation-kubeconfig is provided. When the flag is omitted,
130+
// federationRestConfig remains nil; management controllers will refuse to
131+
// start if --enable-management-controllers is also set.
132+
if federationKubeconfig != "" {
130133
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
131-
&clientcmd.ClientConfigLoadingRules{ExplicitPath: upstreamKubeconfig},
132-
&clientcmd.ConfigOverrides{CurrentContext: upstreamContext},
134+
&clientcmd.ClientConfigLoadingRules{ExplicitPath: federationKubeconfig},
135+
&clientcmd.ConfigOverrides{CurrentContext: federationContext},
133136
)
134137
var err error
135-
upstreamRestConfig, err = loader.ClientConfig()
138+
federationRestConfig, err = loader.ClientConfig()
136139
if err != nil {
137-
setupLog.Error(err, "unable to load upstream kubeconfig", "path", upstreamKubeconfig)
140+
setupLog.Error(err, "unable to load federation kubeconfig", "path", federationKubeconfig)
138141
os.Exit(1)
139142
}
140-
setupLog.Info("upstream kubeconfig loaded", "path", upstreamKubeconfig)
143+
setupLog.Info("federation kubeconfig loaded", "path", federationKubeconfig)
144+
}
145+
146+
// Fail loud: management controllers require a federation kubeconfig. Silently
147+
// skipping them when --enable-management-controllers is set would leave
148+
// federation and instance projection broken with no visible signal — the same
149+
// class of failure as the quota P1 issue. An operator who explicitly enables
150+
// management controllers but omits --federation-kubeconfig has a misconfiguration
151+
// that must surface immediately rather than at runtime.
152+
if enableManagementControllers && federationRestConfig == nil {
153+
setupLog.Error(nil,
154+
"management controllers enabled but no federation kubeconfig configured",
155+
"hint", "set --federation-kubeconfig")
156+
os.Exit(1)
141157
}
142158

143159
setupLog.Info("starting compute",
@@ -240,13 +256,15 @@ func main() {
240256
}
241257
}
242258

243-
// Build a single downstream client shared across all controllers that need
244-
// to read or write to the downstream control plane. Nil when federation is disabled.
245-
var downstreamClient client.Client
246-
if upstreamRestConfig != nil {
247-
downstreamClient, err = client.New(upstreamRestConfig, client.Options{Scheme: scheme})
259+
// Build a single federation client shared across all controllers that need to
260+
// read or write to the Karmada federation control plane. This is the hub that
261+
// the management controllers federate through and that edge cells write back to.
262+
// Nil when --federation-kubeconfig is not set (i.e. federation is disabled).
263+
var federationClient client.Client
264+
if federationRestConfig != nil {
265+
federationClient, err = client.New(federationRestConfig, client.Options{Scheme: scheme})
248266
if err != nil {
249-
setupLog.Error(err, "unable to create downstream client")
267+
setupLog.Error(err, "unable to create federation client")
250268
os.Exit(1)
251269
}
252270
}
@@ -262,7 +280,7 @@ func main() {
262280
clusterNameForProject := func(_ string) multicluster.ClusterName {
263281
return multicluster.ClusterName(singleClusterName)
264282
}
265-
instanceReconciler := &controller.InstanceReconciler{UpstreamClient: downstreamClient}
283+
instanceReconciler := &controller.InstanceReconciler{FederationClient: federationClient}
266284
err = instanceReconciler.SetupWithManager(
267285
mgr,
268286
quotaRestConfig,
@@ -278,10 +296,11 @@ func main() {
278296
}
279297

280298
// WorkloadDeploymentFederator and InstanceProjector are management-plane
281-
// controllers that run on the control-plane cluster. They require a downstream
282-
// control plane to be configured (--upstream-kubeconfig provided).
283-
if enableManagementControllers && upstreamRestConfig != nil {
284-
extra, err := setupManagementControllers(mgr, downstreamClient)
299+
// controllers that run on the control-plane cluster. The fail-loud guard above
300+
// ensures federationRestConfig is non-nil when enableManagementControllers is
301+
// true; the nil check here is a defensive belt-and-suspenders guard.
302+
if enableManagementControllers && federationRestConfig != nil {
303+
extra, err := setupManagementControllers(mgr, federationClient)
285304
if err != nil {
286305
setupLog.Error(err, "unable to set up management controllers")
287306
os.Exit(1)
@@ -427,33 +446,33 @@ func ignoreCanceled(err error) error {
427446

428447
// setupManagementControllers wires the WorkloadDeploymentFederator and
429448
// InstanceProjector onto mgr. It returns any additional Runnable objects that
430-
// must be started alongside the main manager (the downstream manager used by
449+
// must be started alongside the main manager (the federation manager used by
431450
// InstanceProjector). Called only when management controllers are enabled and
432-
// an upstream REST config is available.
433-
func setupManagementControllers(mgr mcmanager.Manager, downstreamClient client.Client) ([]manager.Runnable, error) {
434-
federator := &controller.WorkloadDeploymentFederator{UpstreamClient: downstreamClient}
451+
// a federation REST config is available.
452+
func setupManagementControllers(mgr mcmanager.Manager, federationClient client.Client) ([]manager.Runnable, error) {
453+
federator := &controller.WorkloadDeploymentFederator{FederationClient: federationClient}
435454
if err := federator.SetupWithManager(mgr); err != nil {
436455
return nil, fmt.Errorf("WorkloadDeploymentFederator: %w", err)
437456
}
438457

439-
// InstanceProjector runs in the Control Plane Cell, watches Instances
440-
// written back by POP-cell operators, and projects them into the
441-
// corresponding project namespaces via the multicluster manager.
442-
downstreamMgr, err := manager.New(upstreamRestConfig, manager.Options{
458+
// InstanceProjector runs in the management plane, watches Instances written
459+
// back by POP-cell operators to the Karmada federation control plane, and
460+
// projects them into the corresponding project namespaces via the multicluster manager.
461+
federationMgr, err := manager.New(federationRestConfig, manager.Options{
443462
Scheme: scheme,
444463
Metrics: metricsserver.Options{BindAddress: "0"},
445464
})
446465
if err != nil {
447-
return nil, fmt.Errorf("downstream manager for InstanceProjector: %w", err)
466+
return nil, fmt.Errorf("federation manager for InstanceProjector: %w", err)
448467
}
449468
if err = (&controller.InstanceProjector{
450-
UpstreamClient: downstreamClient,
451-
MCManager: mgr,
452-
}).SetupWithManager(downstreamMgr); err != nil {
469+
FederationClient: federationClient,
470+
MCManager: mgr,
471+
}).SetupWithManager(federationMgr); err != nil {
453472
return nil, fmt.Errorf("InstanceProjector: %w", err)
454473
}
455474

456-
return []manager.Runnable{downstreamMgr}, nil
475+
return []manager.Runnable{federationMgr}, nil
457476
}
458477

459478
// singleModeProjectID returns an InstanceProjectIDFunc for single-cell mode.

config/base/crd/bases/compute.datumapis.com_instances.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ spec:
3535
name: Message
3636
priority: 1
3737
type: string
38+
- jsonPath: .status.conditions[?(@.type=="QuotaGranted")].reason
39+
name: Quota
40+
priority: 1
41+
type: string
3842
name: v1alpha
3943
schema:
4044
openAPIV3Schema:

config/base/manager/manager.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ spec:
3333
- --leader-elect=$(LEADER_ELECT)
3434
- --health-probe-bind-address=$(HEALTH_PROBE_BIND_ADDRESS)
3535
- --server-config=$(SERVER_CONFIG)
36-
- --upstream-kubeconfig=$(UPSTREAM_KUBECONFIG)
36+
- --federation-kubeconfig=$(FEDERATION_KUBECONFIG)
3737
- --enable-management-controllers=$(ENABLE_MANAGEMENT_CONTROLLERS)
3838
- --enable-cell-controllers=$(ENABLE_CELL_CONTROLLERS)
3939
env:
@@ -43,7 +43,7 @@ spec:
4343
value: ":8081"
4444
- name: SERVER_CONFIG
4545
value: /config/config.yaml
46-
- name: UPSTREAM_KUBECONFIG
46+
- name: FEDERATION_KUBECONFIG
4747
value: ""
4848
- name: ENABLE_MANAGEMENT_CONTROLLERS
4949
value: "false"

config/overlays/management-plane/downstream_kubeconfig_patch.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ spec:
88
containers:
99
- name: manager
1010
env:
11-
- name: UPSTREAM_KUBECONFIG
11+
- name: FEDERATION_KUBECONFIG
1212
value: /etc/kubernetes/downstream/auth/downstream-kubeconfig.yaml
1313
volumeMounts:
1414
- name: downstream-kubeconfig

internal/controller/instance_controller.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,14 @@ type InstanceReconciler struct {
137137
// cluster is "single" regardless of project ID. When nil, falls back to
138138
// multicluster.ClusterName(projectID), which is correct for Milo mode.
139139
clusterNameForProject func(projectID string) multicluster.ClusterName
140-
// UpstreamClient is an optional client pointing at the downstream control plane.
140+
// FederationClient is an optional client pointing at the upstream
141+
// Karmada/federation control plane (configured via --federation-kubeconfig).
141142
// When non-nil, the reconciler writes a copy of each Instance back to the
142-
// downstream control plane so that the InstanceProjector (running in the
143+
// federation control plane so that the InstanceProjector (running in the
143144
// management cluster) can aggregate status across all POP cells. Set to nil to
144145
// disable federation write-back (e.g. in non-federation deployments).
145-
UpstreamClient client.Client
146-
finalizers finalizer.Finalizers
146+
FederationClient client.Client
147+
finalizers finalizer.Finalizers
147148
}
148149

149150
// +kubebuilder:rbac:groups=compute.datumapis.com,resources=instances,verbs=get;list;watch;create;update;patch;delete
@@ -409,14 +410,14 @@ func (r *InstanceReconciler) removeQuotaSchedulingGate(ctx context.Context, cl c
409410
// Finalize removes the downstream write-back Instance when the local Instance is
410411
// deleted. It is a no-op when downstream federation is disabled.
411412
func (r *InstanceReconciler) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) {
412-
if r.UpstreamClient == nil {
413+
if r.FederationClient == nil {
413414
return finalizer.Result{}, nil
414415
}
415416

416417
instance := obj.(*computev1alpha.Instance)
417418

418419
downstreamInstance := &computev1alpha.Instance{}
419-
err := r.UpstreamClient.Get(ctx, client.ObjectKeyFromObject(instance), downstreamInstance)
420+
err := r.FederationClient.Get(ctx, client.ObjectKeyFromObject(instance), downstreamInstance)
420421
if apierrors.IsNotFound(err) {
421422
// Already gone — nothing to do.
422423
return finalizer.Result{}, nil
@@ -425,18 +426,18 @@ func (r *InstanceReconciler) Finalize(ctx context.Context, obj client.Object) (f
425426
return finalizer.Result{}, fmt.Errorf("failed getting downstream instance for deletion: %w", err)
426427
}
427428

428-
if err := r.UpstreamClient.Delete(ctx, downstreamInstance); client.IgnoreNotFound(err) != nil {
429+
if err := r.FederationClient.Delete(ctx, downstreamInstance); client.IgnoreNotFound(err) != nil {
429430
return finalizer.Result{}, fmt.Errorf("failed deleting downstream write-back instance: %w", err)
430431
}
431432

432433
return finalizer.Result{}, nil
433434
}
434435

435436
// writeBackToUpstream copies the Instance spec and status to the upstream
436-
// Karmada control plane so that the InstanceProjector can aggregate state from
437-
// all POP cells. It is a no-op when UpstreamClient is nil (federation disabled).
437+
// Karmada/federation control plane so that the InstanceProjector can aggregate
438+
// state from all POP cells. It is a no-op when FederationClient is nil (federation disabled).
438439
func (r *InstanceReconciler) writeBackToUpstream(ctx context.Context, clusterName multicluster.ClusterName, instance *computev1alpha.Instance) error {
439-
if r.UpstreamClient == nil {
440+
if r.FederationClient == nil {
440441
return nil
441442
}
442443

@@ -451,7 +452,7 @@ func (r *InstanceReconciler) writeBackToUpstream(ctx context.Context, clusterNam
451452
// "default"), which the InstanceProjector needs to find the right project cluster.
452453
upstreamNamespace := instance.Namespace // fallback: cell namespace (ns-<uid>)
453454
var downstreamNS corev1.Namespace
454-
if err := r.UpstreamClient.Get(ctx, client.ObjectKey{Name: instance.Namespace}, &downstreamNS); err == nil {
455+
if err := r.FederationClient.Get(ctx, client.ObjectKey{Name: instance.Namespace}, &downstreamNS); err == nil {
455456
if v := downstreamNS.Labels[downstreamclient.UpstreamOwnerNamespaceLabel]; v != "" {
456457
upstreamNamespace = v
457458
}
@@ -473,18 +474,18 @@ func (r *InstanceReconciler) writeBackToUpstream(ctx context.Context, clusterNam
473474
}
474475

475476
existing := &computev1alpha.Instance{}
476-
err := r.UpstreamClient.Get(ctx, client.ObjectKeyFromObject(writeBack), existing)
477+
err := r.FederationClient.Get(ctx, client.ObjectKeyFromObject(writeBack), existing)
477478
if apierrors.IsNotFound(err) {
478479
// Ensure the namespace exists in the downstream control plane before creating the Instance.
479480
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: instance.Namespace}}
480-
if err := r.UpstreamClient.Create(ctx, ns); err != nil && !apierrors.IsAlreadyExists(err) {
481+
if err := r.FederationClient.Create(ctx, ns); err != nil && !apierrors.IsAlreadyExists(err) {
481482
return fmt.Errorf("failed ensuring downstream namespace: %w", err)
482483
}
483-
if err := r.UpstreamClient.Create(ctx, writeBack); err != nil {
484+
if err := r.FederationClient.Create(ctx, writeBack); err != nil {
484485
return fmt.Errorf("failed creating downstream write-back instance: %w", err)
485486
}
486487
writeBack.Status = instance.Status
487-
if err := r.UpstreamClient.Status().Update(ctx, writeBack); err != nil {
488+
if err := r.FederationClient.Status().Update(ctx, writeBack); err != nil {
488489
return fmt.Errorf("failed updating downstream write-back instance status after create: %w", err)
489490
}
490491
return nil
@@ -498,15 +499,15 @@ func (r *InstanceReconciler) writeBackToUpstream(ctx context.Context, clusterNam
498499
!apiequality.Semantic.DeepEqual(existing.Labels, writeBack.Labels) {
499500
existing.Spec = instance.Spec
500501
existing.Labels = writeBack.Labels
501-
if err := r.UpstreamClient.Update(ctx, existing); err != nil {
502+
if err := r.FederationClient.Update(ctx, existing); err != nil {
502503
return fmt.Errorf("failed updating downstream write-back instance: %w", err)
503504
}
504505
}
505506

506507
// Update status only if it differs.
507508
if !apiequality.Semantic.DeepEqual(existing.Status, instance.Status) {
508509
existing.Status = instance.Status
509-
if err := r.UpstreamClient.Status().Update(ctx, existing); err != nil {
510+
if err := r.FederationClient.Status().Update(ctx, existing); err != nil {
510511
return fmt.Errorf("failed updating downstream write-back instance status: %w", err)
511512
}
512513
}

0 commit comments

Comments
 (0)