@@ -6,7 +6,10 @@ import (
66 "strings"
77
88 appsv1 "k8s.io/api/apps/v1"
9+ corev1 "k8s.io/api/core/v1"
910 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+ "k8s.io/apimachinery/pkg/labels"
12+ "k8s.io/apimachinery/pkg/runtime/schema"
1013 "sigs.k8s.io/controller-runtime/pkg/client"
1114 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1215 "sigs.k8s.io/yaml"
@@ -44,7 +47,106 @@ func (r *MaasClusterReconciler) ensureLXDInitializerDS(ctx context.Context, clus
4447 return fmt .Errorf ("failed to get target cluster client: %v" , err )
4548 }
4649
47- // First clean up any existing DaemonSets in this namespace
50+ // If feature is off or cluster is being deleted, we're done
51+ if ! clusterScope .IsLXDHostEnabled () || ! cluster .ObjectMeta .DeletionTimestamp .IsZero () {
52+ return nil
53+ }
54+
55+ // Gate: ensure pivot completed. Require mgmt namespace to have clusterEnv=target
56+ mgmtNS := & corev1.Namespace {}
57+ if err := r .Client .Get (ctx , client.ObjectKey {Name : dsNamespace }, mgmtNS ); err != nil {
58+ // Namespace not readable yet on management cluster; skip for now
59+ return nil
60+ }
61+ if v := strings .TrimSpace (mgmtNS .Annotations ["clusterEnv" ]); v != "target" {
62+ r .Log .Info ("Namespace not marked as target; deferring LXD initializer" , "namespace" , dsNamespace , "clusterEnv" , v )
63+ return nil
64+ }
65+
66+ // Gate: derive desired CP count from MaasCloudConfig; fallback to KCP
67+ desiredCP := int32 (1 )
68+ readyByKCP := int32 (0 )
69+ // Prefer MaasCloudConfig.spec.machinePoolConfig[].size where isControlPlane=true (sum)
70+ {
71+ mccList := & unstructured.UnstructuredList {}
72+ mccList .SetGroupVersionKind (schema.GroupVersionKind {Group : "cluster.spectrocloud.com" , Version : "v1alpha1" , Kind : "MaasCloudConfigList" })
73+ if err := r .Client .List (ctx , mccList , client .InNamespace (dsNamespace )); err == nil {
74+ var sum int64
75+ for _ , it := range mccList .Items {
76+ owned := false
77+ for _ , or := range it .GetOwnerReferences () {
78+ if or .Name == cluster .Name {
79+ owned = true
80+ break
81+ }
82+ }
83+ if ! owned && ! strings .HasSuffix (it .GetName (), "-maas-config" ) {
84+ continue
85+ }
86+ pools , found , _ := unstructured .NestedSlice (it .Object , "spec" , "machinePoolConfig" )
87+ if ! found {
88+ continue
89+ }
90+ for _ , p := range pools {
91+ if mp , ok := p .(map [string ]interface {}); ok {
92+ isCP , _ , _ := unstructured .NestedBool (mp , "isControlPlane" )
93+ if ! isCP {
94+ continue
95+ }
96+ if v , foundSz , _ := unstructured .NestedInt64 (mp , "size" ); foundSz && v > 0 {
97+ sum += v
98+ }
99+ }
100+ }
101+ if sum > 0 {
102+ desiredCP = int32 (sum )
103+ break
104+ }
105+ }
106+ }
107+ }
108+ // Fallback: use KCP if MCC not found
109+ if desiredCP == 1 {
110+ kcpList := & unstructured.UnstructuredList {}
111+ kcpList .SetGroupVersionKind (schema.GroupVersionKind {Group : "controlplane.cluster.x-k8s.io" , Version : "v1beta1" , Kind : "KubeadmControlPlaneList" })
112+ if err := r .Client .List (ctx , kcpList , client .InNamespace (dsNamespace ), client.MatchingLabels {
113+ "cluster.x-k8s.io/cluster-name" : cluster .Name ,
114+ }); err == nil {
115+ if len (kcpList .Items ) > 0 {
116+ item := kcpList .Items [0 ]
117+ if v , found , _ := unstructured .NestedInt64 (item .Object , "spec" , "replicas" ); found && v > 0 {
118+ desiredCP = int32 (v )
119+ }
120+ if v , found , _ := unstructured .NestedInt64 (item .Object , "status" , "readyReplicas" ); found && v >= 0 {
121+ readyByKCP = int32 (v )
122+ }
123+ }
124+ }
125+ }
126+
127+ nodeList := & corev1.NodeList {}
128+ cpSelector := labels .SelectorFromSet (labels.Set {
129+ "node-role.kubernetes.io/control-plane" : "" ,
130+ })
131+ if err := remoteClient .List (ctx , nodeList , & client.ListOptions {LabelSelector : cpSelector }); err == nil {
132+ ready := 0
133+ for _ , n := range nodeList .Items {
134+ for _ , c := range n .Status .Conditions {
135+ if c .Type == corev1 .NodeReady && c .Status == corev1 .ConditionTrue {
136+ ready ++
137+ break
138+ }
139+ }
140+ }
141+ // Proceed when CP nodes are present and Ready, regardless of KCP readyReplicas
142+ if int32 (len (nodeList .Items )) < desiredCP || int32 (ready ) < desiredCP {
143+ r .Log .Info ("Not enough control-plane nodes present/ready yet; skipping DS for now" , "desiredCP" , desiredCP , "readyByKCP" , readyByKCP , "nodeList" , len (nodeList .Items ), "ready" , ready )
144+ // Not enough control-plane nodes present/ready yet; skip DS for now
145+ return nil
146+ }
147+ }
148+
149+ // Clean up any existing DaemonSets in this namespace (old naming/labels)
48150 dsList := & appsv1.DaemonSetList {}
49151 if err := remoteClient .List (ctx , dsList , client .InNamespace (dsNamespace ), client.MatchingLabels {
50152 "app" : "lxd-initializer" ,
@@ -59,11 +161,6 @@ func (r *MaasClusterReconciler) ensureLXDInitializerDS(ctx context.Context, clus
59161 }
60162 }
61163
62- // If feature is off or cluster is being deleted, we're done after cleanup
63- if ! clusterScope .IsLXDHostEnabled () || ! cluster .ObjectMeta .DeletionTimestamp .IsZero () {
64- return nil
65- }
66-
67164 // Ensure RBAC resources are created on target cluster
68165 if err := r .ensureLXDInitializerRBACOnTarget (ctx , remoteClient , dsNamespace ); err != nil {
69166 return fmt .Errorf ("failed to ensure LXD initializer RBAC: %v" , err )
0 commit comments