Skip to content

Commit 4b5290a

Browse files
fix PR: 5711 e2e
1 parent 04a4f62 commit 4b5290a

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

controllers/awsmachinetemplate_controller.go

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
ctrl "sigs.k8s.io/controller-runtime"
3434
"sigs.k8s.io/controller-runtime/pkg/client"
3535
"sigs.k8s.io/controller-runtime/pkg/controller"
36+
"sigs.k8s.io/controller-runtime/pkg/handler"
3637

3738
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
3839
ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/eks/api/v1beta2"
@@ -47,6 +48,11 @@ import (
4748
"sigs.k8s.io/cluster-api/util/predicates"
4849
)
4950

51+
const (
52+
// awsMachineTemplateKind is the Kind name for AWSMachineTemplate resources.
53+
awsMachineTemplateKind = "AWSMachineTemplate"
54+
)
55+
5056
// AWSMachineTemplateReconciler reconciles AWSMachineTemplate objects.
5157
//
5258
// This controller automatically populates capacity information for AWSMachineTemplate resources
@@ -62,11 +68,36 @@ type AWSMachineTemplateReconciler struct {
6268
func (r *AWSMachineTemplateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
6369
log := logger.FromContext(ctx)
6470

65-
return ctrl.NewControllerManagedBy(mgr).
71+
b := ctrl.NewControllerManagedBy(mgr).
6672
For(&infrav1.AWSMachineTemplate{}).
6773
WithOptions(options).
6874
WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(mgr.GetScheme(), log.GetLogger(), r.WatchFilterValue)).
69-
Complete(r)
75+
Watches(
76+
&clusterv1.MachineDeployment{},
77+
handler.EnqueueRequestsFromMapFunc(r.machineDeploymentToAWSMachineTemplate),
78+
)
79+
80+
// Optionally watch KubeadmControlPlane if the type is registered in the scheme
81+
// This is optional because some environments (e.g., EKS) don't have KubeadmControlPlane CRD
82+
if _, err := mgr.GetRESTMapper().RESTMapping(
83+
schema.GroupKind{Group: controlplanev1.GroupVersion.Group, Kind: "KubeadmControlPlane"},
84+
controlplanev1.GroupVersion.Version,
85+
); err == nil {
86+
log.Info("KubeadmControlPlane CRD found, enabling watch")
87+
b = b.Watches(
88+
&controlplanev1.KubeadmControlPlane{},
89+
handler.EnqueueRequestsFromMapFunc(r.kubeadmControlPlaneToAWSMachineTemplate),
90+
)
91+
} else {
92+
log.Info("KubeadmControlPlane CRD not found, skipping watch", "error", err)
93+
}
94+
95+
_, err := b.Build(r)
96+
if err != nil {
97+
return errors.Wrap(err, "failed setting up with a controller manager")
98+
}
99+
100+
return nil
70101
}
71102

72103
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=awsmachinetemplates,verbs=get;list;watch
@@ -420,3 +451,53 @@ func getParentListOptions(obj metav1.ObjectMeta) ([]client.ListOption, error) {
420451
}
421452
return listOpts, nil
422453
}
454+
455+
// kubeadmControlPlaneToAWSMachineTemplate maps KubeadmControlPlane to AWSMachineTemplate reconcile requests.
456+
// This enables the controller to reconcile AWSMachineTemplate when its owner KubeadmControlPlane is created or updated,
457+
// ensuring that nodeInfo can be populated even if the cache hasn't synced yet.
458+
func (r *AWSMachineTemplateReconciler) kubeadmControlPlaneToAWSMachineTemplate(ctx context.Context, o client.Object) []ctrl.Request {
459+
kcp, ok := o.(*controlplanev1.KubeadmControlPlane)
460+
if !ok {
461+
return nil
462+
}
463+
464+
// Check if it references an AWSMachineTemplate
465+
if kcp.Spec.MachineTemplate.Spec.InfrastructureRef.Kind != awsMachineTemplateKind {
466+
return nil
467+
}
468+
469+
// Return reconcile request for the referenced AWSMachineTemplate
470+
return []ctrl.Request{
471+
{
472+
NamespacedName: client.ObjectKey{
473+
Namespace: kcp.Namespace,
474+
Name: kcp.Spec.MachineTemplate.Spec.InfrastructureRef.Name,
475+
},
476+
},
477+
}
478+
}
479+
480+
// machineDeploymentToAWSMachineTemplate maps MachineDeployment to AWSMachineTemplate reconcile requests.
481+
// This enables the controller to reconcile AWSMachineTemplate when its owner MachineDeployment is created or updated,
482+
// ensuring that nodeInfo can be populated even if the cache hasn't synced yet.
483+
func (r *AWSMachineTemplateReconciler) machineDeploymentToAWSMachineTemplate(ctx context.Context, o client.Object) []ctrl.Request {
484+
md, ok := o.(*clusterv1.MachineDeployment)
485+
if !ok {
486+
return nil
487+
}
488+
489+
// Check if it references an AWSMachineTemplate
490+
if md.Spec.Template.Spec.InfrastructureRef.Kind != awsMachineTemplateKind {
491+
return nil
492+
}
493+
494+
// Return reconcile request for the referenced AWSMachineTemplate
495+
return []ctrl.Request{
496+
{
497+
NamespacedName: client.ObjectKey{
498+
Namespace: md.Namespace,
499+
Name: md.Spec.Template.Spec.InfrastructureRef.Name,
500+
},
501+
},
502+
}
503+
}

test/e2e/suites/unmanaged/unmanaged_functional_test.go

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() {
338338
configCluster.ControlPlaneMachineCount = ptr.To[int64](1)
339339
configCluster.WorkerMachineCount = ptr.To[int64](1)
340340
configCluster.Flavor = shared.SSMFlavor
341-
_, md, _ := createCluster(ctx, configCluster, result)
341+
cluster, md, _ := createCluster(ctx, configCluster, result)
342342

343343
workerMachines := framework.GetMachinesByMachineDeployments(ctx, framework.GetMachinesByMachineDeploymentsInput{
344344
Lister: e2eCtx.Environment.BootstrapClusterProxy.GetClient(),
@@ -360,19 +360,61 @@ var _ = ginkgo.Context("[unmanaged] [functional]", func() {
360360
Expect(err).To(BeNil())
361361
Expect(len(awsMachineTemplateList.Items)).To(BeNumerically(">", 0), "Expected at least one AWSMachineTemplate")
362362

363+
ginkgo.By(fmt.Sprintf("Found %d AWSMachineTemplates", len(awsMachineTemplateList.Items)))
364+
ginkgo.By(fmt.Sprintf("Cluster: name=%s, namespace=%s, infrastructureRef=%v",
365+
cluster.Name, cluster.Namespace, cluster.Spec.InfrastructureRef))
366+
367+
// Print each AWSMachineTemplate for debugging
368+
for i, template := range awsMachineTemplateList.Items {
369+
ginkgo.By(fmt.Sprintf("AWSMachineTemplate[%d]: %+v", i, template))
370+
}
371+
363372
foundTemplateWithCapacity := false
364373
foundTemplateWithNodeInfo := false
365-
for _, template := range awsMachineTemplateList.Items {
374+
ginkgo.By(fmt.Sprintf("DEBUG: Found %d AWSMachineTemplates in namespace %s", len(awsMachineTemplateList.Items), namespace.Name))
375+
for i, template := range awsMachineTemplateList.Items {
376+
ginkgo.By(fmt.Sprintf("DEBUG: Template[%d] - Name: %s", i, template.Name))
377+
ginkgo.By(fmt.Sprintf("DEBUG: Template[%d] - Spec.Template.Spec.InstanceType: %s", i, template.Spec.Template.Spec.InstanceType))
378+
ginkgo.By(fmt.Sprintf("DEBUG: Template[%d] - Status.Capacity: %v", i, template.Status.Capacity))
379+
ginkgo.By(fmt.Sprintf("DEBUG: Template[%d] - Status.NodeInfo: %+v", i, template.Status.NodeInfo))
380+
ginkgo.By(fmt.Sprintf("DEBUG: Template[%d] - Status.Conditions: %+v", i, template.Status.Conditions))
381+
366382
if len(template.Status.Capacity) > 0 {
367383
foundTemplateWithCapacity = true
368384
ginkgo.By(fmt.Sprintf("AWSMachineTemplate %s has capacity populated: %v", template.Name, template.Status.Capacity))
369385
Expect(template.Status.Capacity).To(HaveKey(corev1.ResourceCPU), "Expected CPU to be set in capacity")
370386
Expect(template.Status.Capacity).To(HaveKey(corev1.ResourceMemory), "Expected Memory to be set in capacity")
387+
} else {
388+
ginkgo.By(fmt.Sprintf("WARNING: Template %s has NO capacity populated", template.Name))
371389
}
372390
if template.Status.NodeInfo != nil {
373391
foundTemplateWithNodeInfo = true
374392
ginkgo.By(fmt.Sprintf("AWSMachineTemplate %s has nodeInfo populated: %v", template.Name, template.Status.NodeInfo))
375393
// Verify architecture is set (should be either amd64 or arm64 for AWS)
394+
ginkgo.By(fmt.Sprintf("DEBUG: Template %s - NodeInfo.Architecture: '%s' (len: %d)", template.Name, template.Status.NodeInfo.Architecture, len(template.Status.NodeInfo.Architecture)))
395+
ginkgo.By(fmt.Sprintf("DEBUG: Template %s - NodeInfo.OperatingSystem: '%s' (len: %d)", template.Name, template.Status.NodeInfo.OperatingSystem, len(template.Status.NodeInfo.OperatingSystem)))
396+
397+
// Verify architecture is set (should be either amd64 or arm64 for AWS)
398+
if len(template.Status.NodeInfo.Architecture) == 0 {
399+
ginkgo.By(fmt.Sprintf("ERROR: Template %s has empty Architecture field in NodeInfo", template.Name))
400+
}
401+
Expect(template.Status.NodeInfo.Architecture).ToNot(BeEmpty(), fmt.Sprintf("Expected architecture to be set in nodeInfo for template %s", template.Name))
402+
Expect(string(template.Status.NodeInfo.Architecture)).To(MatchRegexp("^(amd64|arm64)$"), fmt.Sprintf("Expected architecture to be amd64 or arm64 for template %s", template.Name))
403+
// Verify operating system is set (if available)
404+
// Note: OperatingSystem may be empty when:
405+
// 1. No explicit AMI ID is specified
406+
// 2. Kubernetes version cannot be determined (MachineDeployment not yet created)
407+
// 3. AMI lookup fails
408+
// In these cases, the controller falls back to instance type architecture only.
409+
// See: controllers/awsmachinetemplate_controller.go:319-323 (Strategy 3)
410+
if len(template.Status.NodeInfo.OperatingSystem) == 0 {
411+
ginkgo.By(fmt.Sprintf("INFO: Template %s has empty OperatingSystem (this is acceptable in fallback scenarios)", template.Name))
412+
ginkgo.By(fmt.Sprintf("DEBUG: Template %s full status: %+v", template.Name, template.Status))
413+
} else {
414+
ginkgo.By(fmt.Sprintf("INFO: Template %s has OperatingSystem set to: %s", template.Name, template.Status.NodeInfo.OperatingSystem))
415+
// If OperatingSystem is set, verify it's a valid value
416+
Expect(string(template.Status.NodeInfo.OperatingSystem)).To(MatchRegexp("^(linux|windows)$"), fmt.Sprintf("Expected operatingSystem to be linux or windows for template %s", template.Name))
417+
}
376418
Expect(template.Status.NodeInfo.Architecture).ToNot(BeEmpty(), "Expected architecture to be set in nodeInfo")
377419
Expect(string(template.Status.NodeInfo.Architecture)).To(MatchRegexp("^(amd64|arm64)$"), "Expected architecture to be amd64 or arm64")
378420
// Verify operating system is set

0 commit comments

Comments
 (0)