@@ -30,6 +30,7 @@ import (
3030 ekstypes "github.com/aws/aws-sdk-go-v2/service/eks/types"
3131 "github.com/aws/smithy-go"
3232 corev1 "k8s.io/api/core/v1"
33+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3334 "k8s.io/client-go/tools/record"
3435 karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
3536 "sigs.k8s.io/karpenter/pkg/events"
@@ -271,6 +272,26 @@ var _ = Describe("NodeClass Validation Status Controller", func() {
271272 }, fake .MaxCalls (1 ))
272273 }, nodeclass .ConditionReasonCreateLaunchTemplateAuthFailed ),
273274 )
275+ Context ("Windows AMI Validation" , func () {
276+ DescribeTable (
277+ "should fallback to static instance types when windows ami is used" ,
278+ func (family string , terms []v1.AMISelectorTerm , expectedInstanceType ec2types.InstanceType , expectedAMIID string ) {
279+ // Skip Windows 2025 on versions < 1.35
280+ if family == v1 .AMIFamilyWindows2025 && version .MustParseGeneric (awsEnv .VersionProvider .Get (ctx )).Minor () < 35 {
281+ Skip ("Windows 2025 requires EKS version 1.35+, current version: " + awsEnv .VersionProvider .Get (ctx ))
282+ }
283+ nodeClass .Spec .AMIFamily = lo .ToPtr (family )
284+ nodeClass .Spec .AMISelectorTerms = terms
285+ ExpectApplied (ctx , env .Client , nodeClass )
286+ ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
287+ runInstancesInput := awsEnv .EC2API .RunInstancesBehavior .CalledWithInput .Pop ()
288+ Expect (runInstancesInput .InstanceType ).To (Equal (expectedInstanceType ))
289+ },
290+ Entry ("Windows2019 with m5.large" , v1 .AMIFamilyWindows2019 , []v1.AMISelectorTerm {{Alias : "windows2019@latest" }}, ec2types .InstanceTypeM5Large , "amd64-ami-id" ),
291+ Entry ("Windows2022 with m5.large" , v1 .AMIFamilyWindows2022 , []v1.AMISelectorTerm {{Alias : "windows2022@latest" }}, ec2types .InstanceTypeM5Large , "amd64-ami-id" ),
292+ Entry ("Windows2025 with m6g.large" , v1 .AMIFamilyWindows2025 , []v1.AMISelectorTerm {{Alias : "windows2025@latest" }}, ec2types .InstanceTypeM6gLarge , "arm64-ami-id" ),
293+ )
294+ })
274295 Context ("Instance Type Prioritization Validation" , func () {
275296 BeforeEach (func () {
276297 awsEnv .EC2API .DescribeImagesOutput .Set (& ec2.DescribeImagesOutput {
@@ -307,37 +328,6 @@ var _ = Describe("NodeClass Validation Status Controller", func() {
307328 Expect (awsEnv .InstanceTypesProvider .UpdateInstanceTypes (ctx )).To (Succeed ())
308329 Expect (awsEnv .InstanceTypesProvider .UpdateInstanceTypeOfferings (ctx )).To (Succeed ())
309330 })
310- DescribeTable (
311- "should fallback to static instance types when windows ami is used" ,
312- func (family string , terms []v1.AMISelectorTerm , expectedInstanceType ec2types.InstanceType , expectedAMIID string ) {
313- // Skip Windows 2025 on versions < 1.35
314- if family == v1 .AMIFamilyWindows2025 && version .MustParseGeneric (awsEnv .VersionProvider .Get (ctx )).Minor () < 35 {
315- Skip ("Windows 2025 requires EKS version 1.35+, current version: " + awsEnv .VersionProvider .Get (ctx ))
316- }
317- nodeClass .Spec .AMIFamily = lo .ToPtr (family )
318- nodeClass .Spec .AMISelectorTerms = []v1.AMISelectorTerm {
319- {
320- Tags : map [string ]string {"*" : "*" },
321- },
322- }
323- // Filter DescribeImages to use only the expected AMI
324- allImages := awsEnv .EC2API .DescribeImagesOutput .Clone ().Images
325- awsEnv .EC2API .DescribeImagesOutput .Set (& ec2.DescribeImagesOutput {
326- Images : lo .Filter (allImages , func (img ec2types.Image , _ int ) bool {
327- return lo .FromPtr (img .ImageId ) == expectedAMIID
328- }),
329- })
330- ExpectApplied (ctx , env .Client , nodeClass )
331- ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
332- launchTemplateInput := awsEnv .EC2API .CreateLaunchTemplateBehavior .CalledWithInput .Pop ()
333- runInstancesInput := awsEnv .EC2API .RunInstancesBehavior .CalledWithInput .Pop ()
334- Expect (runInstancesInput .InstanceType ).To (Equal (expectedInstanceType ))
335- Expect (launchTemplateInput .LaunchTemplateData .ImageId ).To (PointTo (Equal (expectedAMIID )))
336- },
337- Entry ("Windows2019 with m5.large" , v1 .AMIFamilyWindows2019 , []v1.AMISelectorTerm {{Alias : "windows2019@latest" }}, ec2types .InstanceTypeM5Large , "amd64-ami-id" ),
338- Entry ("Windows2022 with m5.large" , v1 .AMIFamilyWindows2022 , []v1.AMISelectorTerm {{Alias : "windows2022@latest" }}, ec2types .InstanceTypeM5Large , "amd64-ami-id" ),
339- Entry ("Windows2025 with m6g.large" , v1 .AMIFamilyWindows2025 , []v1.AMISelectorTerm {{Alias : "windows2025@latest" }}, ec2types .InstanceTypeM6gLarge , "arm64-ami-id" ),
340- )
341331 It ("should prioritize non-GPU instances" , func () {
342332 nodePool := coretest .NodePool (karpv1.NodePool {Spec : karpv1.NodePoolSpec {Template : karpv1.NodeClaimTemplate {
343333 Spec : karpv1.NodeClaimTemplateSpec {
@@ -518,4 +508,86 @@ var _ = Describe("NodeClass Validation Status Controller", func() {
518508 Expect (awsEnv .EC2API .CreateLaunchTemplateBehavior .Calls ()).To (Equal (0 ))
519509 Expect (awsEnv .EC2API .RunInstancesBehavior .Calls ()).To (Equal (0 ))
520510 })
511+ Context ("Cache Key Generation" , func () {
512+ It ("should generate same cache key when NodePools are in different order" , func () {
513+ nodePool1 := coretest .NodePool (karpv1.NodePool {
514+ ObjectMeta : metav1.ObjectMeta {Name : "np-aaa" },
515+ Spec : karpv1.NodePoolSpec {Template : karpv1.NodeClaimTemplate {
516+ Spec : karpv1.NodeClaimTemplateSpec {
517+ Requirements : []karpv1.NodeSelectorRequirementWithMinValues {
518+ {Key : corev1 .LabelInstanceTypeStable , Operator : corev1 .NodeSelectorOpIn , Values : []string {"m5.large" }},
519+ },
520+ NodeClassRef : & karpv1.NodeClassReference {
521+ Group : object .GVK (nodeClass ).Group ,
522+ Kind : object .GVK (nodeClass ).Kind ,
523+ Name : nodeClass .Name ,
524+ },
525+ },
526+ }}})
527+ nodePool2 := coretest .NodePool (karpv1.NodePool {
528+ ObjectMeta : metav1.ObjectMeta {Name : "np-zzz" },
529+ Spec : karpv1.NodePoolSpec {Template : karpv1.NodeClaimTemplate {
530+ Spec : karpv1.NodeClaimTemplateSpec {
531+ Requirements : []karpv1.NodeSelectorRequirementWithMinValues {
532+ {Key : corev1 .LabelInstanceTypeStable , Operator : corev1 .NodeSelectorOpIn , Values : []string {"m5.xlarge" }},
533+ },
534+ NodeClassRef : & karpv1.NodeClassReference {
535+ Group : object .GVK (nodeClass ).Group ,
536+ Kind : object .GVK (nodeClass ).Kind ,
537+ Name : nodeClass .Name ,
538+ },
539+ },
540+ }}})
541+
542+ // Apply in one order
543+ ExpectApplied (ctx , env .Client , nodeClass , nodePool1 , nodePool2 )
544+ ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
545+ items1 := awsEnv .ValidationCache .Items ()
546+ Expect (items1 ).To (HaveLen (1 ))
547+ key1 := lo .Keys (items1 )[0 ]
548+
549+ // Clear cache and apply in different order
550+ awsEnv .ValidationCache .Flush ()
551+ ExpectApplied (ctx , env .Client , nodeClass , nodePool2 , nodePool1 )
552+ ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
553+ items2 := awsEnv .ValidationCache .Items ()
554+ Expect (items2 ).To (HaveLen (1 ))
555+ key2 := lo .Keys (items2 )[0 ]
556+
557+ // Keys should be identical
558+ Expect (key1 ).To (Equal (key2 ))
559+ })
560+ It ("should generate different cache key when NodePool requirements change" , func () {
561+ nodePool := coretest .NodePool (karpv1.NodePool {Spec : karpv1.NodePoolSpec {Template : karpv1.NodeClaimTemplate {
562+ Spec : karpv1.NodeClaimTemplateSpec {
563+ Requirements : []karpv1.NodeSelectorRequirementWithMinValues {
564+ {Key : corev1 .LabelInstanceTypeStable , Operator : corev1 .NodeSelectorOpIn , Values : []string {"m5.large" }},
565+ },
566+ NodeClassRef : & karpv1.NodeClassReference {
567+ Group : object .GVK (nodeClass ).Group ,
568+ Kind : object .GVK (nodeClass ).Kind ,
569+ Name : nodeClass .Name ,
570+ },
571+ },
572+ }}})
573+
574+ ExpectApplied (ctx , env .Client , nodeClass , nodePool )
575+ ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
576+ items1 := awsEnv .ValidationCache .Items ()
577+ Expect (items1 ).To (HaveLen (1 ))
578+ key1 := lo .Keys (items1 )[0 ]
579+
580+ // Update NodePool requirements
581+ nodePool .Spec .Template .Spec .Requirements [0 ].Values = []string {"m5.xlarge" }
582+ ExpectApplied (ctx , env .Client , nodePool )
583+ ExpectObjectReconciled (ctx , env .Client , controller , nodeClass )
584+ items2 := awsEnv .ValidationCache .Items ()
585+ Expect (items2 ).To (HaveLen (2 )) // Should have both old and new cache entries
586+ keys := lo .Keys (items2 )
587+
588+ // Should have a new key
589+ Expect (keys ).To (ContainElement (key1 ))
590+ Expect (keys ).To (ContainElement (Not (Equal (key1 ))))
591+ })
592+ })
521593})
0 commit comments