ami_test.go•27.1 kB
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package nodeclass_test
import (
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/version"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1"
"github.com/aws/karpenter-provider-aws/pkg/test"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "sigs.k8s.io/karpenter/pkg/test/expectations"
)
var _ = Describe("NodeClass AMI Status Controller", func() {
var k8sVersion string
BeforeEach(func() {
k8sVersion = awsEnv.VersionProvider.Get(ctx)
nodeClass = test.EC2NodeClass(v1.EC2NodeClass{
Spec: v1.EC2NodeClassSpec{
SubnetSelectorTerms: []v1.SubnetSelectorTerm{
{
Tags: map[string]string{"*": "*"},
},
},
SecurityGroupSelectorTerms: []v1.SecurityGroupSelectorTerm{
{
Tags: map[string]string{"*": "*"},
},
},
AMIFamily: lo.ToPtr(v1.AMIFamilyCustom),
AMISelectorTerms: []v1.AMISelectorTerm{
{
Tags: map[string]string{"*": "*"},
},
},
},
})
awsEnv.EC2API.DescribeImagesOutput.Set(&ec2.DescribeImagesOutput{
Images: []ec2types.Image{
{
Name: aws.String("amd64-standard"),
ImageId: aws.String("ami-amd64-standard"),
CreationDate: aws.String(time.Now().Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("amd64-standard")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("amd64-standard-new"),
ImageId: aws.String("ami-amd64-standard-new"),
CreationDate: aws.String(time.Now().Add(time.Minute).Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("amd64-standard")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("amd64-nvidia"),
ImageId: aws.String("ami-amd64-nvidia"),
CreationDate: aws.String(time.Now().Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("amd64-nvidia")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("amd64-neuron"),
ImageId: aws.String("ami-amd64-neuron"),
CreationDate: aws.String(time.Now().Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("amd64-neuron")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("arm64-standard"),
ImageId: aws.String("ami-arm64-standard"),
CreationDate: aws.String(time.Now().Format(time.RFC3339)),
Architecture: "arm64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("arm64-standard")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("arm64-nvidia"),
ImageId: aws.String("ami-arm64-nvidia"),
CreationDate: aws.String(time.Now().Format(time.RFC3339)),
Architecture: "arm64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("arm64-nvidia")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
},
})
})
Context("Aliases", func() {
It("Should resolve all AMIs with correct requirements for AL2023", func() {
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2023/x86_64/standard/recommended/image_id", k8sVersion): "ami-amd64-standard",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2023/x86_64/nvidia/recommended/image_id", k8sVersion): "ami-amd64-nvidia",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2023/x86_64/neuron/recommended/image_id", k8sVersion): "ami-amd64-neuron",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2023/arm64/standard/recommended/image_id", k8sVersion): "ami-arm64-standard",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2023/arm64/nvidia/recommended/image_id", k8sVersion): "ami-arm64-nvidia",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{Alias: "al2023@latest"}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(5))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
{
Name: "amd64-nvidia",
ID: "ami-amd64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
{
Name: "amd64-neuron",
ID: "ami-amd64-neuron",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
{
Name: "arm64-standard",
ID: "ami-arm64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
{
Name: "arm64-nvidia",
ID: "ami-arm64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("Should resolve all AMIs with correct requirements for AL2", func() {
if version.MustParseGeneric(k8sVersion).Minor() > 32 {
Skip("AL2 is not supported on versions > 1.32")
}
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2/recommended/image_id", k8sVersion): "ami-amd64-standard",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2-gpu/recommended/image_id", k8sVersion): "ami-amd64-nvidia",
fmt.Sprintf("/aws/service/eks/optimized-ami/%s/amazon-linux-2-arm64/recommended/image_id", k8sVersion): "ami-arm64-standard",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{Alias: "al2@latest"}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(4))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
{
Name: "amd64-nvidia",
ID: "ami-amd64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
// Note: AL2 uses the same AMI for nvidia and neuron, we use the nvidia AMI here
{
Name: "amd64-nvidia",
ID: "ami-amd64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
{
Name: "arm64-standard",
ID: "ami-arm64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("Should resolve all AMIs with correct requirements for Bottlerocket", func() {
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/x86_64/latest/image_id", k8sVersion): "ami-amd64-standard",
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/arm64/latest/image_id", k8sVersion): "ami-arm64-standard",
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s-nvidia/x86_64/latest/image_id", k8sVersion): "ami-amd64-nvidia",
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s-nvidia/arm64/latest/image_id", k8sVersion): "ami-arm64-nvidia",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{Alias: "bottlerocket@latest"}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(5))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
{
Name: "arm64-standard",
ID: "ami-arm64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
// Note: Bottlerocket uses the same AMI for standard and neuron
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
{
Name: "amd64-nvidia",
ID: "ami-amd64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
{
Name: "arm64-nvidia",
ID: "ami-arm64-nvidia",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpExists,
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("Should resolve all AMIs with correct requirements for Windows2019", func() {
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/ami-windows-latest/Windows_Server-2019-English-Core-EKS_Optimized-%s/image_id", k8sVersion): "ami-amd64-standard",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{Alias: "windows2019@latest"}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(1))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelOSStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{string(corev1.Windows)},
},
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: corev1.LabelWindowsBuild,
Operator: corev1.NodeSelectorOpIn,
Values: []string{v1.Windows2019Build},
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("Should resolve all AMIs with correct requirements for Windows2022", func() {
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/ami-windows-latest/Windows_Server-2022-English-Core-EKS_Optimized-%s/image_id", k8sVersion): "ami-amd64-standard",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{Alias: "windows2022@latest"}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(1))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelOSStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{string(corev1.Windows)},
},
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: corev1.LabelWindowsBuild,
Operator: corev1.NodeSelectorOpIn,
Values: []string{v1.Windows2022Build},
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
})
It("should resolve amiSelector AMIs and requirements into status when all SSM parameters don't resolve", func() {
// This parameter set doesn't include any of the Nvidia AMIs
awsEnv.SSMAPI.Parameters = map[string]string{
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/x86_64/latest/image_id", k8sVersion): "ami-amd64-standard",
fmt.Sprintf("/aws/service/bottlerocket/aws-k8s-%s/arm64/latest/image_id", k8sVersion): "ami-arm64-standard",
}
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{
Alias: "bottlerocket@latest",
}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(3))
Expect(nodeClass.Status.AMIs).To(ContainElements([]v1.AMI{
{
Name: "arm64-standard",
ID: "ami-arm64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
{
Name: "amd64-standard",
ID: "ami-amd64-standard",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
{
Key: v1.LabelInstanceGPUCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
{
Key: v1.LabelInstanceAcceleratorCount,
Operator: corev1.NodeSelectorOpDoesNotExist,
},
},
},
}))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("should resolve a valid AMI selector", func() {
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{{
Tags: map[string]string{"Name": "amd64-standard"},
}}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(nodeClass.Status.AMIs).To(Equal(
[]v1.AMI{
{
Name: "amd64-standard-new",
ID: "ami-amd64-standard-new",
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
}},
},
},
))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("should get error when resolving AMIs and have status condition set to false", func() {
awsEnv.EC2API.NextError.Set(fmt.Errorf("unable to resolve AMI"))
ExpectApplied(ctx, env.Client, nodeClass)
_ = ExpectObjectReconcileFailed(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeFalse())
})
Context("NodeClass AMI Status", func() {
BeforeEach(func() {
// Set time using the injectable/fake clock to now
awsEnv.Clock.SetTime(time.Now())
nodeClass.Spec.AMISelectorTerms = []v1.AMISelectorTerm{
{
Tags: map[string]string{"*": "*"},
},
}
awsEnv.EC2API.DescribeImagesOutput.Set(&ec2.DescribeImagesOutput{
Images: []ec2types.Image{
{
Name: aws.String("test-ami-3"),
ImageId: aws.String("ami-id-789"),
CreationDate: aws.String("2021-08-31T00:12:42.000Z"),
DeprecationTime: aws.String(awsEnv.Clock.Now().Add(30 * time.Minute).Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-ami-3")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("test-ami-2"),
ImageId: aws.String("ami-id-456"),
CreationDate: aws.String("2021-08-31T00:12:42.000Z"),
Architecture: "arm64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-ami-2")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
},
})
})
It("should update nodeclass AMI status with correct deprecation value and conditions", func() {
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(2))
Expect(nodeClass.Status.AMIs).To(Equal(
[]v1.AMI{
{
Name: "test-ami-2",
ID: "ami-id-456",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
},
},
{
Name: "test-ami-3",
ID: "ami-id-789",
Requirements: []corev1.NodeSelectorRequirement{
{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
},
},
},
))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
// Increment clock to simulate status updates on deprecated AMIs
awsEnv.Clock.Step(40 * time.Minute)
// Flush Cache
awsEnv.AMICache.Flush()
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(2))
Expect(nodeClass.Status.AMIs).To(Equal(
[]v1.AMI{
{
Name: "test-ami-2",
ID: "ami-id-456",
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
},
},
{
Name: "test-ami-3",
ID: "ami-id-789",
// Adds deprecated field to the AMI status on the NodeClass
Deprecated: true,
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
},
},
},
))
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
It("should remove AMIDeprecated status condition when non deprecated AMIs are discovered", func() {
// Increment clock to simulate status updates on deprecated AMIs
awsEnv.Clock.Step(40 * time.Minute)
// Initial reconcile discovers AMIs which are deprecated
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(2))
Expect(nodeClass.Status.AMIs).To(Equal(
[]v1.AMI{
{
Name: "test-ami-2",
ID: "ami-id-456",
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
}},
},
{
Name: "test-ami-3",
ID: "ami-id-789",
// Adds deprecated field to the AMI status on the NodeClass
Deprecated: true,
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
}},
},
},
))
// Checks if both AMIsReady and AMIsDeprecated status conditions are set
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
// rediscover AMIs again and reconcile
awsEnv.EC2API.DescribeImagesOutput.Set(&ec2.DescribeImagesOutput{
Images: []ec2types.Image{
{
Name: aws.String("test-ami-4"),
ImageId: aws.String("ami-id-123"),
CreationDate: aws.String("2021-08-31T00:12:42.000Z"),
DeprecationTime: aws.String(awsEnv.Clock.Now().Add(30 * time.Minute).Format(time.RFC3339)),
Architecture: "x86_64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-ami-4")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
{
Name: aws.String("test-ami-2"),
ImageId: aws.String("ami-id-456"),
CreationDate: aws.String("2021-08-31T00:12:42.000Z"),
Architecture: "arm64",
Tags: []ec2types.Tag{
{Key: aws.String("Name"), Value: aws.String("test-ami-2")},
{Key: aws.String("foo"), Value: aws.String("bar")},
},
State: ec2types.ImageStateAvailable,
},
},
})
awsEnv.AMICache.Flush()
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(len(nodeClass.Status.AMIs)).To(Equal(2))
Expect(nodeClass.Status.AMIs).To(Equal(
[]v1.AMI{
{
Name: "test-ami-4",
ID: "ami-id-123",
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureAmd64},
},
},
},
{
Name: "test-ami-2",
ID: "ami-id-456",
Requirements: []corev1.NodeSelectorRequirement{{
Key: corev1.LabelArchStable,
Operator: corev1.NodeSelectorOpIn,
Values: []string{karpv1.ArchitectureArm64},
},
},
},
},
))
// Since all AMIs discovered are non deprecated, the status conditions should remove AMIsDeprecated and only set AMIsReady
Expect(nodeClass.StatusConditions().IsTrue(v1.ConditionTypeAMIsReady)).To(BeTrue())
})
})
})