diff --git a/go.mod b/go.mod index b8d2c9d51f15..b89a78c49d35 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/aws/karpenter-provider-aws go 1.22.5 +replace sigs.k8s.io/karpenter v0.37.1-0.20240629051434-89a81c3ae853 => github.com/engedaam/karpenter-core v0.0.0-20240705112536-1d148ce95425 + require ( github.com/Pallinder/go-randomdata v1.2.0 github.com/PuerkitoBio/goquery v1.9.2 diff --git a/go.sum b/go.sum index 41b1aa55a8da..1692564770db 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/engedaam/karpenter-core v0.0.0-20240705112536-1d148ce95425 h1:5e4bB/QsWN1/XuhOCDU49rjbJOvQyw76MpK3RGkUDS8= +github.com/engedaam/karpenter-core v0.0.0-20240705112536-1d148ce95425/go.mod h1:Capmf9EwVSBoANOkP5qy4xHBaZ9/y0wUDyh4UzgRJlk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -763,8 +765,6 @@ sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHv sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/karpenter v0.37.1-0.20240629051434-89a81c3ae853 h1:WAFMsZJpnScxrsXBsbou0hsZJOQRFS1RxRXb4Ee/cs4= -sigs.k8s.io/karpenter v0.37.1-0.20240629051434-89a81c3ae853/go.mod h1:jPA1J954ZvzJelythD9EtkrQXZLPUrZaMhQJ4MBRQ/Q= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml b/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml index 1de256a30958..ee7e1993c414 100644 --- a/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml +++ b/pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml @@ -717,7 +717,7 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} - name: v1beta1 @@ -1293,6 +1293,6 @@ spec: type: object type: object served: true - storage: true + storage: false subresources: status: {} diff --git a/pkg/apis/v1/ec2nodeclass.go b/pkg/apis/v1/ec2nodeclass.go index fda4c4c3e71a..4dac7e882417 100644 --- a/pkg/apis/v1/ec2nodeclass.go +++ b/pkg/apis/v1/ec2nodeclass.go @@ -21,7 +21,7 @@ import ( "github.com/samber/lo" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corev1beta1 "sigs.k8s.io/karpenter/pkg/apis/v1beta1" + v1 "sigs.k8s.io/karpenter/pkg/apis/v1" ) // EC2NodeClassSpec is the top level specification for the AWS Karpenter Provider. @@ -186,7 +186,7 @@ type AMISelectorTerm struct { // KubeletConfiguration defines args to be used when configuring kubelet on provisioned nodes. // They are a subset of the upstream types, recognizing not all options may be supported. // Wherever possible, the types and names should reflect the upstream kubelet types. -// https://pkg.go.dev/k8s.io/kubelet/config/v1beta1#KubeletConfiguration +// https://pkg.go.dev/k8s.io/kubelet/config/v1#KubeletConfiguration // https://github.com/kubernetes/kubernetes/blob/9f82d81e55cafdedab619ea25cabf5d42736dacf/cmd/kubelet/app/options/options.go#L53 type KubeletConfiguration struct { // clusterDNS is a list of IP addresses for the cluster DNS server. @@ -395,6 +395,7 @@ const ( // EC2NodeClass is the Schema for the EC2NodeClass API // +kubebuilder:object:root=true +// +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="" // +kubebuilder:printcolumn:name="Role",type="string",JSONPath=".spec.role",priority=1,description="" @@ -436,7 +437,7 @@ func (in *EC2NodeClass) InstanceProfileRole() string { func (in *EC2NodeClass) InstanceProfileTags(clusterName string) map[string]string { return lo.Assign(in.Spec.Tags, map[string]string{ fmt.Sprintf("kubernetes.io/cluster/%s", clusterName): "owned", - corev1beta1.ManagedByAnnotationKey: clusterName, + v1.ManagedByAnnotationKey: clusterName, LabelNodeClass: in.Name, }) } diff --git a/pkg/apis/v1/ec2nodeclass_conversion.go b/pkg/apis/v1/ec2nodeclass_conversion.go new file mode 100644 index 000000000000..f53902d57ca0 --- /dev/null +++ b/pkg/apis/v1/ec2nodeclass_conversion.go @@ -0,0 +1,173 @@ +/* +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 v1 + +import ( + "context" + + "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" + "github.com/samber/lo" + "knative.dev/pkg/apis" +) + +func (in *EC2NodeClass) ConvertTo(ctx context.Context, to apis.Convertible) error { + v1beta1enc := to.(*v1beta1.EC2NodeClass) + v1beta1enc.ObjectMeta = in.ObjectMeta + + in.Spec.convertTo(&v1beta1enc.Spec) + in.Status.convertTo((&v1beta1enc.Status)) + return nil +} + +func (in *EC2NodeClassSpec) convertTo(v1beta1enc *v1beta1.EC2NodeClassSpec) { + v1beta1enc.SubnetSelectorTerms = lo.Map(in.SubnetSelectorTerms, func(subnet SubnetSelectorTerm, _ int) v1beta1.SubnetSelectorTerm { + return v1beta1.SubnetSelectorTerm{ + ID: subnet.ID, + Tags: subnet.Tags, + } + }) + v1beta1enc.SecurityGroupSelectorTerms = lo.Map(in.SecurityGroupSelectorTerms, func(sg SecurityGroupSelectorTerm, _ int) v1beta1.SecurityGroupSelectorTerm { + return v1beta1.SecurityGroupSelectorTerm{ + ID: sg.ID, + Name: sg.Name, + Tags: sg.Tags, + } + }) + v1beta1enc.AMISelectorTerms = lo.Map(in.AMISelectorTerms, func(ami AMISelectorTerm, _ int) v1beta1.AMISelectorTerm { + return v1beta1.AMISelectorTerm{ + ID: ami.ID, + Name: ami.Name, + Owner: ami.Owner, + Tags: ami.Tags, + } + }) + v1beta1enc.AMIFamily = in.AMIFamily + v1beta1enc.AssociatePublicIPAddress = in.AssociatePublicIPAddress + v1beta1enc.Context = in.Context + v1beta1enc.DetailedMonitoring = in.DetailedMonitoring + v1beta1enc.Role = in.Role + v1beta1enc.InstanceProfile = in.InstanceProfile + v1beta1enc.InstanceStorePolicy = (*v1beta1.InstanceStorePolicy)(in.InstanceStorePolicy) + v1beta1enc.Tags = in.Tags + v1beta1enc.UserData = in.UserData + v1beta1enc.MetadataOptions = (*v1beta1.MetadataOptions)(in.MetadataOptions) + v1beta1enc.BlockDeviceMappings = lo.Map(in.BlockDeviceMappings, func(bdm *BlockDeviceMapping, _ int) *v1beta1.BlockDeviceMapping { + return &v1beta1.BlockDeviceMapping{ + DeviceName: bdm.DeviceName, + RootVolume: bdm.RootVolume, + EBS: (*v1beta1.BlockDevice)(bdm.EBS), + } + }) +} + +func (in *EC2NodeClassStatus) convertTo(v1beta1enc *v1beta1.EC2NodeClassStatus) { + v1beta1enc.Subnets = lo.Map(in.Subnets, func(subnet Subnet, _ int) v1beta1.Subnet { + return v1beta1.Subnet{ + ID: subnet.ID, + Zone: subnet.Zone, + ZoneID: subnet.ZoneID, + } + }) + v1beta1enc.SecurityGroups = lo.Map(in.SecurityGroups, func(sg SecurityGroup, _ int) v1beta1.SecurityGroup { + return v1beta1.SecurityGroup{ + ID: sg.ID, + Name: sg.Name, + } + }) + v1beta1enc.AMIs = lo.Map(in.AMIs, func(ami AMI, _ int) v1beta1.AMI { + return v1beta1.AMI{ + ID: ami.ID, + Name: ami.Name, + Requirements: ami.Requirements, + } + }) + v1beta1enc.InstanceProfile = in.InstanceProfile + v1beta1enc.Conditions = in.Conditions +} + +func (in *EC2NodeClass) ConvertFrom(ctx context.Context, from apis.Convertible) error { + v1beta1enc := from.(*v1beta1.EC2NodeClass) + in.ObjectMeta = v1beta1enc.ObjectMeta + + in.Spec.convertFrom(&v1beta1enc.Spec) + in.Status.convertFrom((&v1beta1enc.Status)) + return nil +} + +func (in *EC2NodeClassSpec) convertFrom(v1beta1enc *v1beta1.EC2NodeClassSpec) { + in.SubnetSelectorTerms = lo.Map(v1beta1enc.SubnetSelectorTerms, func(subnet v1beta1.SubnetSelectorTerm, _ int) SubnetSelectorTerm { + return SubnetSelectorTerm{ + ID: subnet.ID, + Tags: subnet.Tags, + } + }) + in.SecurityGroupSelectorTerms = lo.Map(v1beta1enc.SecurityGroupSelectorTerms, func(sg v1beta1.SecurityGroupSelectorTerm, _ int) SecurityGroupSelectorTerm { + return SecurityGroupSelectorTerm{ + ID: sg.ID, + Name: sg.Name, + Tags: sg.Tags, + } + }) + in.AMISelectorTerms = lo.Map(v1beta1enc.AMISelectorTerms, func(ami v1beta1.AMISelectorTerm, _ int) AMISelectorTerm { + return AMISelectorTerm{ + ID: ami.ID, + Name: ami.Name, + Owner: ami.Owner, + Tags: ami.Tags, + } + }) + in.AMIFamily = v1beta1enc.AMIFamily + in.AssociatePublicIPAddress = v1beta1enc.AssociatePublicIPAddress + in.Context = v1beta1enc.Context + in.DetailedMonitoring = v1beta1enc.DetailedMonitoring + in.Role = v1beta1enc.Role + in.InstanceProfile = v1beta1enc.InstanceProfile + in.InstanceStorePolicy = (*InstanceStorePolicy)(v1beta1enc.InstanceStorePolicy) + in.Tags = v1beta1enc.Tags + in.UserData = v1beta1enc.UserData + in.MetadataOptions = (*MetadataOptions)(v1beta1enc.MetadataOptions) + in.BlockDeviceMappings = lo.Map(v1beta1enc.BlockDeviceMappings, func(bdm *v1beta1.BlockDeviceMapping, _ int) *BlockDeviceMapping { + return &BlockDeviceMapping{ + DeviceName: bdm.DeviceName, + RootVolume: bdm.RootVolume, + EBS: (*BlockDevice)(bdm.EBS), + } + }) +} + +func (in *EC2NodeClassStatus) convertFrom(v1beta1enc *v1beta1.EC2NodeClassStatus) { + in.Subnets = lo.Map(v1beta1enc.Subnets, func(subnet v1beta1.Subnet, _ int) Subnet { + return Subnet{ + ID: subnet.ID, + Zone: subnet.Zone, + ZoneID: subnet.ZoneID, + } + }) + in.SecurityGroups = lo.Map(v1beta1enc.SecurityGroups, func(sg v1beta1.SecurityGroup, _ int) SecurityGroup { + return SecurityGroup{ + ID: sg.ID, + Name: sg.Name, + } + }) + in.AMIs = lo.Map(v1beta1enc.AMIs, func(ami v1beta1.AMI, _ int) AMI { + return AMI{ + ID: ami.ID, + Name: ami.Name, + Requirements: ami.Requirements, + } + }) + in.InstanceProfile = v1beta1enc.InstanceProfile + in.Conditions = v1beta1enc.Conditions +} diff --git a/pkg/apis/v1/ec2nodeclass_conversion_test.go b/pkg/apis/v1/ec2nodeclass_conversion_test.go new file mode 100644 index 000000000000..70a60fcf1e52 --- /dev/null +++ b/pkg/apis/v1/ec2nodeclass_conversion_test.go @@ -0,0 +1,489 @@ +/* +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 v1_test + +import ( + "github.com/awslabs/operatorpkg/status" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/samber/lo" + "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/karpenter/pkg/test" + + . "github.com/aws/karpenter-provider-aws/pkg/apis/v1" + "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" +) + +var _ = Describe("Convert v1 to v1beta1 EC2NodeClass API", func() { + var ( + v1ec2nodeclass *EC2NodeClass + v1beta1ec2nodeclass *v1beta1.EC2NodeClass + ) + + BeforeEach(func() { + v1ec2nodeclass = &EC2NodeClass{} + v1beta1ec2nodeclass = &v1beta1.EC2NodeClass{} + }) + + It("should convert v1 ec2nodeclass metadata", func() { + v1ec2nodeclass.ObjectMeta = test.ObjectMeta() + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1beta1ec2nodeclass.ObjectMeta).To(BeEquivalentTo(v1ec2nodeclass.ObjectMeta)) + }) + Context("EC2NodeClass Spec", func() { + It("should convert v1 ec2nodeclass subnet selector terms", func() { + v1ec2nodeclass.Spec.SubnetSelectorTerms = []SubnetSelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Spec.SubnetSelectorTerms { + Expect(v1beta1ec2nodeclass.Spec.SubnetSelectorTerms[i].Tags).To(Equal(v1ec2nodeclass.Spec.SubnetSelectorTerms[i].Tags)) + Expect(v1beta1ec2nodeclass.Spec.SubnetSelectorTerms[i].ID).To(Equal(v1ec2nodeclass.Spec.SubnetSelectorTerms[i].ID)) + } + }) + It("should convert v1 ec2nodeclass securitygroup selector terms", func() { + v1ec2nodeclass.Spec.SecurityGroupSelectorTerms = []SecurityGroupSelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + Name: "test-name-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + Name: "test-name-2", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Spec.SecurityGroupSelectorTerms { + Expect(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Tags).To(Equal(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Tags)) + Expect(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].ID).To(Equal(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].ID)) + Expect(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Name).To(Equal(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Name)) + } + }) + It("should convert v1 ec2nodeclass ami selector terms", func() { + v1ec2nodeclass.Spec.AMISelectorTerms = []AMISelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + Name: "test-name-1", + Owner: "test-owner-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + Name: "test-name-2", + Owner: "test-owner-1", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Spec.AMISelectorTerms { + Expect(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Tags).To(Equal(v1ec2nodeclass.Spec.AMISelectorTerms[i].Tags)) + Expect(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].ID).To(Equal(v1ec2nodeclass.Spec.AMISelectorTerms[i].ID)) + Expect(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Name).To(Equal(v1ec2nodeclass.Spec.AMISelectorTerms[i].Name)) + Expect(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Owner).To(Equal(v1ec2nodeclass.Spec.AMISelectorTerms[i].Owner)) + } + }) + It("should convert v1 ec2nodeclass associate public ip address ", func() { + v1ec2nodeclass.Spec.AssociatePublicIPAddress = lo.ToPtr(true) + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.AssociatePublicIPAddress)).To(BeTrue()) + }) + It("should convert v1 ec2nodeclass ami family", func() { + v1ec2nodeclass.Spec.AMIFamily = &AMIFamilyUbuntu + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.AMIFamily)).To(Equal(v1beta1.AMIFamilyUbuntu)) + }) + It("should convert v1 ec2nodeclass user data", func() { + v1ec2nodeclass.Spec.UserData = lo.ToPtr("test user data") + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.UserData)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.UserData))) + }) + It("should convert v1 ec2nodeclass role", func() { + v1ec2nodeclass.Spec.Role = "test-role" + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1beta1ec2nodeclass.Spec.Role).To(Equal(v1ec2nodeclass.Spec.Role)) + }) + It("should convert v1 ec2nodeclass instance profile", func() { + v1ec2nodeclass.Spec.InstanceProfile = lo.ToPtr("test-instance-profile") + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.InstanceProfile)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.InstanceProfile))) + }) + It("should convert v1 ec2nodeclass tags", func() { + v1ec2nodeclass.Spec.Tags = map[string]string{ + "test-key-tag-1": "test-value-tag-1", + "test-key-tag-2": "test-value-tag-2", + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1beta1ec2nodeclass.Spec.Tags).To(Equal(v1ec2nodeclass.Spec.Tags)) + }) + It("should convert v1 ec2nodeclass block device mapping", func() { + v1ec2nodeclass.Spec.BlockDeviceMappings = []*BlockDeviceMapping{ + { + EBS: &BlockDevice{ + DeleteOnTermination: lo.ToPtr(true), + Encrypted: lo.ToPtr(true), + IOPS: lo.ToPtr(int64(45123)), + KMSKeyID: lo.ToPtr("test-kms-id"), + SnapshotID: lo.ToPtr("test-snapshot-id"), + Throughput: lo.ToPtr(int64(4512433)), + VolumeSize: lo.ToPtr(resource.MustParse("54G")), + VolumeType: lo.ToPtr("test-type"), + }, + DeviceName: lo.ToPtr("test-device"), + RootVolume: true, + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Spec.BlockDeviceMappings { + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].RootVolume).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].RootVolume)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].DeviceName).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].DeviceName)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.DeleteOnTermination).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.DeleteOnTermination)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Encrypted).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Encrypted)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.IOPS).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.IOPS)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.KMSKeyID).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.KMSKeyID)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.SnapshotID).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.SnapshotID)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Throughput).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Throughput)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeSize).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeSize)) + Expect(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeType).To(Equal(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeType)) + } + }) + It("should convert v1 ec2nodeclass instance store policy", func() { + v1ec2nodeclass.Spec.InstanceStorePolicy = lo.ToPtr(InstanceStorePolicyRAID0) + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(string(lo.FromPtr(v1beta1ec2nodeclass.Spec.InstanceStorePolicy))).To(Equal(string(lo.FromPtr(v1ec2nodeclass.Spec.InstanceStorePolicy)))) + }) + It("should convert v1 ec2nodeclass detailed monitoring", func() { + v1ec2nodeclass.Spec.DetailedMonitoring = lo.ToPtr(true) + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.DetailedMonitoring)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.DetailedMonitoring))) + }) + It("should convert v1 ec2nodeclass metadata options", func() { + v1ec2nodeclass.Spec.MetadataOptions = &MetadataOptions{ + HTTPEndpoint: lo.ToPtr("test-endpoint"), + HTTPProtocolIPv6: lo.ToPtr("test-protocol"), + HTTPPutResponseHopLimit: lo.ToPtr(int64(54)), + HTTPTokens: lo.ToPtr("test-token"), + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPEndpoint)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPEndpoint))) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPProtocolIPv6)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPProtocolIPv6))) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPPutResponseHopLimit)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPPutResponseHopLimit))) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPTokens)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPTokens))) + }) + It("should convert v1 ec2nodeclass context", func() { + v1ec2nodeclass.Spec.Context = lo.ToPtr("test-context") + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1beta1ec2nodeclass.Spec.Context)).To(Equal(lo.FromPtr(v1ec2nodeclass.Spec.Context))) + }) + }) + Context("EC2NodeClass Status", func() { + It("should convert v1 ec2nodeclass subnet", func() { + v1ec2nodeclass.Status.Subnets = []Subnet{ + { + ID: "test-id", + Zone: "test-zone", + ZoneID: "test-zone-id", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Status.Subnets { + Expect(v1beta1ec2nodeclass.Status.Subnets[i].ID).To(Equal(v1ec2nodeclass.Status.Subnets[i].ID)) + Expect(v1beta1ec2nodeclass.Status.Subnets[i].Zone).To(Equal(v1ec2nodeclass.Status.Subnets[i].Zone)) + Expect(v1beta1ec2nodeclass.Status.Subnets[i].ZoneID).To(Equal(v1ec2nodeclass.Status.Subnets[i].ZoneID)) + } + }) + It("should convert v1 ec2nodeclass security group ", func() { + v1ec2nodeclass.Status.SecurityGroups = []SecurityGroup{ + { + ID: "test-id", + Name: "test-name", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Status.SecurityGroups { + Expect(v1beta1ec2nodeclass.Status.SecurityGroups[i].ID).To(Equal(v1ec2nodeclass.Status.SecurityGroups[i].ID)) + Expect(v1beta1ec2nodeclass.Status.SecurityGroups[i].Name).To(Equal(v1ec2nodeclass.Status.SecurityGroups[i].Name)) + } + }) + It("should convert v1 ec2nodeclass ami", func() { + v1ec2nodeclass.Status.AMIs = []AMI{ + { + ID: "test-id", + Name: "test-name", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1ec2nodeclass.Status.AMIs { + Expect(v1beta1ec2nodeclass.Status.AMIs[i].ID).To(Equal(v1ec2nodeclass.Status.AMIs[i].ID)) + Expect(v1beta1ec2nodeclass.Status.AMIs[i].Name).To(Equal(v1ec2nodeclass.Status.AMIs[i].Name)) + + } + }) + It("should convert v1 ec2nodeclass instance profile", func() { + v1ec2nodeclass.Status.InstanceProfile = "test-instance-profile" + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1beta1ec2nodeclass.Status.InstanceProfile).To(Equal(v1ec2nodeclass.Status.InstanceProfile)) + }) + It("should convert v1 ec2nodeclass conditions", func() { + v1ec2nodeclass.Status.Conditions = []status.Condition{ + { + Status: status.ConditionReady, + Reason: "test-reason", + }, + } + Expect(v1ec2nodeclass.ConvertTo(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1beta1ec2nodeclass.Status.Conditions).To(Equal(v1beta1ec2nodeclass.Status.Conditions)) + }) + }) +}) + +var _ = Describe("Convert v1beta1 to v1 EC2NodeClass API", func() { + var ( + v1ec2nodeclass *EC2NodeClass + v1beta1ec2nodeclass *v1beta1.EC2NodeClass + ) + + BeforeEach(func() { + v1ec2nodeclass = &EC2NodeClass{} + v1beta1ec2nodeclass = &v1beta1.EC2NodeClass{} + }) + + It("should convert v1beta1 ec2nodeclass metadata", func() { + v1beta1ec2nodeclass.ObjectMeta = test.ObjectMeta() + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1ec2nodeclass.ObjectMeta).To(BeEquivalentTo(v1beta1ec2nodeclass.ObjectMeta)) + }) + Context("EC2NodeClass Spec", func() { + It("should convert v1beta1 ec2nodeclass subnet selector terms", func() { + v1beta1ec2nodeclass.Spec.SubnetSelectorTerms = []v1beta1.SubnetSelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Spec.SubnetSelectorTerms { + Expect(v1ec2nodeclass.Spec.SubnetSelectorTerms[i].Tags).To(Equal(v1beta1ec2nodeclass.Spec.SubnetSelectorTerms[i].Tags)) + Expect(v1ec2nodeclass.Spec.SubnetSelectorTerms[i].ID).To(Equal(v1beta1ec2nodeclass.Spec.SubnetSelectorTerms[i].ID)) + } + }) + It("should convert v1beta1 ec2nodeclass securitygroup selector terms", func() { + v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms = []v1beta1.SecurityGroupSelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + Name: "test-name-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + Name: "test-name-2", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms { + Expect(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Tags).To(Equal(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Tags)) + Expect(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].ID).To(Equal(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].ID)) + Expect(v1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Name).To(Equal(v1beta1ec2nodeclass.Spec.SecurityGroupSelectorTerms[i].Name)) + } + }) + It("should convert v1beta1 ec2nodeclass ami selector terms", func() { + v1beta1ec2nodeclass.Spec.AMISelectorTerms = []v1beta1.AMISelectorTerm{ + { + Tags: map[string]string{"test-key-1": "test-value-1"}, + ID: "test-id-1", + Name: "test-name-1", + Owner: "test-owner-1", + }, + { + Tags: map[string]string{"test-key-2": "test-value-2"}, + ID: "test-id-2", + Name: "test-name-2", + Owner: "test-owner-1", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Spec.AMISelectorTerms { + Expect(v1ec2nodeclass.Spec.AMISelectorTerms[i].Tags).To(Equal(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Tags)) + Expect(v1ec2nodeclass.Spec.AMISelectorTerms[i].ID).To(Equal(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].ID)) + Expect(v1ec2nodeclass.Spec.AMISelectorTerms[i].Name).To(Equal(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Name)) + Expect(v1ec2nodeclass.Spec.AMISelectorTerms[i].Owner).To(Equal(v1beta1ec2nodeclass.Spec.AMISelectorTerms[i].Owner)) + } + }) + It("should convert v1beta1 ec2nodeclass associate public ip address ", func() { + v1beta1ec2nodeclass.Spec.AssociatePublicIPAddress = lo.ToPtr(true) + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.AssociatePublicIPAddress)).To(BeTrue()) + }) + It("should convert v1beta1 ec2nodeclass ami family", func() { + v1beta1ec2nodeclass.Spec.AMIFamily = &AMIFamilyUbuntu + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.AMIFamily)).To(Equal(v1beta1.AMIFamilyUbuntu)) + }) + It("should convert v1beta1 ec2nodeclass user data", func() { + v1beta1ec2nodeclass.Spec.UserData = lo.ToPtr("test user data") + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.UserData)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.UserData))) + }) + It("should convert v1beta1 ec2nodeclass role", func() { + v1beta1ec2nodeclass.Spec.Role = "test-role" + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1ec2nodeclass.Spec.Role).To(Equal(v1beta1ec2nodeclass.Spec.Role)) + }) + It("should convert v1beta1 ec2nodeclass instance profile", func() { + v1beta1ec2nodeclass.Spec.InstanceProfile = lo.ToPtr("test-instance-profile") + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.InstanceProfile)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.InstanceProfile))) + }) + It("should convert v1beta1 ec2nodeclass tags", func() { + v1beta1ec2nodeclass.Spec.Tags = map[string]string{ + "test-key-tag-1": "test-value-tag-1", + "test-key-tag-2": "test-value-tag-2", + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1ec2nodeclass.Spec.Tags).To(Equal(v1beta1ec2nodeclass.Spec.Tags)) + }) + It("should convert v1beta1 ec2nodeclass block device mapping", func() { + v1beta1ec2nodeclass.Spec.BlockDeviceMappings = []*v1beta1.BlockDeviceMapping{ + { + EBS: &v1beta1.BlockDevice{ + DeleteOnTermination: lo.ToPtr(true), + Encrypted: lo.ToPtr(true), + IOPS: lo.ToPtr(int64(45123)), + KMSKeyID: lo.ToPtr("test-kms-id"), + SnapshotID: lo.ToPtr("test-snapshot-id"), + Throughput: lo.ToPtr(int64(4512433)), + VolumeSize: lo.ToPtr(resource.MustParse("54G")), + VolumeType: lo.ToPtr("test-type"), + }, + DeviceName: lo.ToPtr("test-device"), + RootVolume: true, + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Spec.BlockDeviceMappings { + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].RootVolume).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].RootVolume)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].DeviceName).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].DeviceName)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.DeleteOnTermination).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.DeleteOnTermination)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Encrypted).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Encrypted)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.IOPS).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.IOPS)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.KMSKeyID).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.KMSKeyID)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.SnapshotID).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.SnapshotID)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Throughput).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.Throughput)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeSize).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeSize)) + Expect(v1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeType).To(Equal(v1beta1ec2nodeclass.Spec.BlockDeviceMappings[i].EBS.VolumeType)) + } + }) + It("should convert v1beta1 ec2nodeclass instance store policy", func() { + v1beta1ec2nodeclass.Spec.InstanceStorePolicy = lo.ToPtr(v1beta1.InstanceStorePolicyRAID0) + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(string(lo.FromPtr(v1ec2nodeclass.Spec.InstanceStorePolicy))).To(Equal(string(lo.FromPtr(v1beta1ec2nodeclass.Spec.InstanceStorePolicy)))) + }) + It("should convert v1beta1 ec2nodeclass detailed monitoring", func() { + v1beta1ec2nodeclass.Spec.DetailedMonitoring = lo.ToPtr(true) + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.DetailedMonitoring)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.DetailedMonitoring))) + }) + It("should convert v1beta1 ec2nodeclass metadata options", func() { + v1beta1ec2nodeclass.Spec.MetadataOptions = &v1beta1.MetadataOptions{ + HTTPEndpoint: lo.ToPtr("test-endpoint"), + HTTPProtocolIPv6: lo.ToPtr("test-protocol"), + HTTPPutResponseHopLimit: lo.ToPtr(int64(54)), + HTTPTokens: lo.ToPtr("test-token"), + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPEndpoint)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPEndpoint))) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPProtocolIPv6)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPProtocolIPv6))) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPPutResponseHopLimit)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPPutResponseHopLimit))) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.MetadataOptions.HTTPTokens)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.MetadataOptions.HTTPTokens))) + }) + It("should convert v1beta1 ec2nodeclass context", func() { + v1beta1ec2nodeclass.Spec.Context = lo.ToPtr("test-context") + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(lo.FromPtr(v1ec2nodeclass.Spec.Context)).To(Equal(lo.FromPtr(v1beta1ec2nodeclass.Spec.Context))) + }) + }) + Context("EC2NodeClass Status", func() { + It("should convert v1beta1 ec2nodeclass subnet", func() { + v1beta1ec2nodeclass.Status.Subnets = []v1beta1.Subnet{ + { + ID: "test-id", + Zone: "test-zone", + ZoneID: "test-zone-id", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Status.Subnets { + Expect(v1ec2nodeclass.Status.Subnets[i].ID).To(Equal(v1beta1ec2nodeclass.Status.Subnets[i].ID)) + Expect(v1ec2nodeclass.Status.Subnets[i].Zone).To(Equal(v1beta1ec2nodeclass.Status.Subnets[i].Zone)) + Expect(v1ec2nodeclass.Status.Subnets[i].ZoneID).To(Equal(v1beta1ec2nodeclass.Status.Subnets[i].ZoneID)) + } + }) + It("should convert v1beta1 ec2nodeclass security group ", func() { + v1beta1ec2nodeclass.Status.SecurityGroups = []v1beta1.SecurityGroup{ + { + ID: "test-id", + Name: "test-name", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Status.SecurityGroups { + Expect(v1ec2nodeclass.Status.SecurityGroups[i].ID).To(Equal(v1beta1ec2nodeclass.Status.SecurityGroups[i].ID)) + Expect(v1ec2nodeclass.Status.SecurityGroups[i].Name).To(Equal(v1beta1ec2nodeclass.Status.SecurityGroups[i].Name)) + } + }) + It("should convert v1beta1 ec2nodeclass ami", func() { + v1beta1ec2nodeclass.Status.AMIs = []v1beta1.AMI{ + { + ID: "test-id", + Name: "test-name", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + for i := range v1beta1ec2nodeclass.Status.AMIs { + Expect(v1ec2nodeclass.Status.AMIs[i].ID).To(Equal(v1beta1ec2nodeclass.Status.AMIs[i].ID)) + Expect(v1ec2nodeclass.Status.AMIs[i].Name).To(Equal(v1beta1ec2nodeclass.Status.AMIs[i].Name)) + + } + }) + It("should convert v1beta1 ec2nodeclass instance profile", func() { + v1beta1ec2nodeclass.Status.InstanceProfile = "test-instance-profile" + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1ec2nodeclass.Status.InstanceProfile).To(Equal(v1beta1ec2nodeclass.Status.InstanceProfile)) + }) + It("should convert v1beta1 ec2nodeclass conditions", func() { + v1beta1ec2nodeclass.Status.Conditions = []status.Condition{ + { + Status: status.ConditionReady, + Reason: "test-reason", + }, + } + Expect(v1ec2nodeclass.ConvertFrom(ctx, v1beta1ec2nodeclass)).To(Succeed()) + Expect(v1ec2nodeclass.Status.Conditions).To(Equal(v1beta1ec2nodeclass.Status.Conditions)) + }) + }) +}) diff --git a/pkg/apis/v1/ec2nodeclass_hash_test.go b/pkg/apis/v1/ec2nodeclass_hash_test.go index afed0631aaed..1d3d6d153632 100644 --- a/pkg/apis/v1/ec2nodeclass_hash_test.go +++ b/pkg/apis/v1/ec2nodeclass_hash_test.go @@ -15,6 +15,8 @@ limitations under the License. package v1_test import ( + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/imdario/mergo" "github.com/samber/lo" @@ -152,6 +154,18 @@ var _ = Describe("Hash", func() { Entry("BlockDeviceMapping SnapshotID", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{BlockDeviceMappings: []*v1.BlockDeviceMapping{{EBS: &v1.BlockDevice{SnapshotID: lo.ToPtr("test")}}}}}), Entry("BlockDeviceMapping Throughput", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{BlockDeviceMappings: []*v1.BlockDeviceMapping{{EBS: &v1.BlockDevice{Throughput: lo.ToPtr(int64(10))}}}}}), Entry("BlockDeviceMapping VolumeType", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{BlockDeviceMappings: []*v1.BlockDeviceMapping{{EBS: &v1.BlockDevice{VolumeType: lo.ToPtr("io1")}}}}}), + Entry("Kubelet ClusterDNS", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{ClusterDNS: []string{"test-dns"}}}}), + Entry("Kubelet MaxPods", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{MaxPods: lo.ToPtr(int32(10))}}}), + Entry("Kubelet PodsPerCore", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{PodsPerCore: lo.ToPtr(int32(31))}}}), + Entry("Kubelet SystemReserved", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{SystemReserved: map[string]string{"test-key-1": "test-value-1"}}}}), + Entry("Kubelet KubeReserved", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{KubeReserved: map[string]string{"test-key-2": "test-value-2"}}}}), + Entry("Kubelet EvictionHard", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{EvictionHard: map[string]string{"test-key-3": "test-value-3"}}}}), + Entry("Kubelet EvictionSoft", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{EvictionSoft: map[string]string{"test-key-4": "test-value-4"}}}}), + Entry("Kubelet EvictionSoftGracePeriod", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{EvictionSoftGracePeriod: map[string]metav1.Duration{"test-key": metav1.Duration{Duration: time.Minute}}}}}), + Entry("Kubelet EvictionMaxPodGracePeriod", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{EvictionMaxPodGracePeriod: lo.ToPtr(int32(92))}}}), + Entry("Kubelet ImageGCHighThresholdPercent", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{ImageGCHighThresholdPercent: lo.ToPtr(int32(23))}}}), + Entry("Kubelet ImageGCLowThresholdPercent", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{ImageGCLowThresholdPercent: lo.ToPtr(int32(334))}}}), + Entry("Kubelet CPUCFSQuota", v1.EC2NodeClass{Spec: v1.EC2NodeClassSpec{Kubelet: &v1.KubeletConfiguration{CPUCFSQuota: lo.ToPtr(true)}}}), ) // We create a separate test for updating blockDeviceMapping volumeSize, since resource.Quantity is a struct, and mergo.WithSliceDeepCopy // doesn't work well with unexported fields, like the ones that are present in resource.Quantity diff --git a/pkg/apis/v1/ec2nodeclass_validation_cel_test.go b/pkg/apis/v1/ec2nodeclass_validation_cel_test.go index 46207267293e..bfb9e7014d94 100644 --- a/pkg/apis/v1/ec2nodeclass_validation_cel_test.go +++ b/pkg/apis/v1/ec2nodeclass_validation_cel_test.go @@ -19,15 +19,15 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/samber/lo" - corev1 "k8s.io/api/core/corev1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/corev1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/ptr" - corev1 "sigs.k8s.io/karpenter/pkg/apis/corev1" + corev1 "sigs.k8s.io/karpenter/pkg/apis/v1" coretest "sigs.k8s.io/karpenter/pkg/test" - corev1 "github.com/aws/karpenter-provider-aws/pkg/apis/corev1" + providerv1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" "github.com/aws/karpenter-provider-aws/pkg/test" . "github.com/onsi/ginkgo/v2" @@ -35,7 +35,7 @@ import ( ) var _ = Describe("CEL/Validation", func() { - var nc *corev1.EC2NodeClass + var nc *providerv1.EC2NodeClass BeforeEach(func() { if env.Version.Minor() < 25 { @@ -91,7 +91,7 @@ var _ = Describe("CEL/Validation", func() { } Expect(env.Client.Create(ctx, nc)).To(Not(Succeed())) nc.Spec.Tags = map[string]string{ - corev1.LabelNodeClass: "test", + providerv1.LabelNodeClass: "test", } Expect(env.Client.Create(ctx, nc)).To(Not(Succeed())) nc.Spec.Tags = map[string]string{ @@ -102,7 +102,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("SubnetSelectorTerms", func() { It("should succeed with a valid subnet selector on tags", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -112,7 +112,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid subnet selector on id", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { ID: "subnet-12345749", }, @@ -124,17 +124,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when no subnet selector terms exist", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{} + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{} Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a subnet selector term has no values", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ {}, } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a subnet selector term has no tag map values", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { Tags: map[string]string{}, }, @@ -142,7 +142,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a subnet selector term has a tag map key that is empty", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { Tags: map[string]string{ "test": "", @@ -152,7 +152,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a subnet selector term has a tag map value that is empty", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { Tags: map[string]string{ "": "testvalue", @@ -162,7 +162,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when the last subnet selector is invalid", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -187,7 +187,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with tags", func() { - nc.Spec.SubnetSelectorTerms = []corev1.SubnetSelectorTerm{ + nc.Spec.SubnetSelectorTerms = []providerv1.SubnetSelectorTerm{ { ID: "subnet-12345749", Tags: map[string]string{ @@ -200,7 +200,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("SecurityGroupSelectorTerms", func() { It("should succeed with a valid security group selector on tags", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -210,7 +210,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid security group selector on id", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { ID: "sg-12345749", }, @@ -218,7 +218,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid security group selector on name", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Name: "testname", }, @@ -230,17 +230,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when no security group selector terms exist", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{} + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{} Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a security group selector term has no values", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ {}, } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a security group selector term has no tag map values", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Tags: map[string]string{}, }, @@ -248,7 +248,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a security group selector term has a tag map key that is empty", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Tags: map[string]string{ "test": "", @@ -258,7 +258,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a security group selector term has a tag map value that is empty", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Tags: map[string]string{ "": "testvalue", @@ -268,7 +268,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when the last security group selector is invalid", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -293,7 +293,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with tags", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { ID: "sg-12345749", Tags: map[string]string{ @@ -304,7 +304,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with name", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { ID: "sg-12345749", Name: "my-security-group", @@ -313,7 +313,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying name with tags", func() { - nc.Spec.SecurityGroupSelectorTerms = []corev1.SecurityGroupSelectorTerm{ + nc.Spec.SecurityGroupSelectorTerms = []providerv1.SecurityGroupSelectorTerm{ { Name: "my-security-group", Tags: map[string]string{ @@ -326,7 +326,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("AMISelectorTerms", func() { It("should succeed with a valid ami selector on tags", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -336,7 +336,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid ami selector on id", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { ID: "ami-12345749", }, @@ -344,7 +344,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid ami selector on name", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Name: "testname", }, @@ -352,7 +352,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed with a valid ami selector on name and owner", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Name: "testname", Owner: "testowner", @@ -361,7 +361,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should succeed when an ami selector term has an owner key with tags", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Owner: "testowner", Tags: map[string]string{ @@ -372,13 +372,13 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail when a ami selector term has no values", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ {}, } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a ami selector term has no tag map values", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Tags: map[string]string{}, }, @@ -386,7 +386,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a ami selector term has a tag map key that is empty", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Tags: map[string]string{ "test": "", @@ -396,7 +396,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when a ami selector term has a tag map value that is empty", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Tags: map[string]string{ "": "testvalue", @@ -406,7 +406,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when the last ami selector is invalid", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { Tags: map[string]string{ "test": "testvalue", @@ -431,7 +431,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with tags", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { ID: "ami-12345749", Tags: map[string]string{ @@ -442,7 +442,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with name", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { ID: "ami-12345749", Name: "my-custom-ami", @@ -451,7 +451,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when specifying id with owner", func() { - nc.Spec.AMISelectorTerms = []corev1.AMISelectorTerm{ + nc.Spec.AMISelectorTerms = []providerv1.AMISelectorTerm{ { ID: "ami-12345749", Owner: "123456789", @@ -460,23 +460,23 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when AMIFamily is Custom and not AMISelectorTerms", func() { - nc.Spec.AMIFamily = &corev1.AMIFamilyCustom + nc.Spec.AMIFamily = &providerv1.AMIFamilyCustom Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) }) Context("Kubelet", func() { It("should fail on kubeReserved with invalid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ KubeReserved: map[string]string{ - string(corev1.ResourcePods): "2", + string(v1.ResourcePods): "2", }, } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on systemReserved with invalid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ SystemReserved: map[string]string{ - string(corev1.ResourcePods): "2", + string(v1.ResourcePods): "2", }, } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) @@ -484,7 +484,7 @@ var _ = Describe("CEL/Validation", func() { Context("Eviction Signals", func() { Context("Eviction Hard", func() { It("should succeed on evictionHard with valid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory.available": "5%", "nodefs.available": "10%", @@ -497,7 +497,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail on evictionHard with invalid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory": "5%", }, @@ -505,7 +505,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid formatted percentage value in evictionHard", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory.available": "5%3", }, @@ -513,7 +513,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid percentage value (too large) in evictionHard", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory.available": "110%", }, @@ -521,7 +521,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid quantity value in evictionHard", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory.available": "110GB", }, @@ -532,7 +532,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("Eviction Soft", func() { It("should succeed on evictionSoft with valid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "5%", "nodefs.available": "10%", @@ -553,7 +553,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail on evictionSoft with invalid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory": "5%", }, @@ -564,7 +564,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid formatted percentage value in evictionSoft", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "5%3", }, @@ -575,7 +575,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid percentage value (too large) in evictionSoft", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "110%", }, @@ -586,7 +586,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail on invalid quantity value in evictionSoft", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "110GB", }, @@ -597,7 +597,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when eviction soft doesn't have matching grace period", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "200Mi", }, @@ -607,20 +607,20 @@ var _ = Describe("CEL/Validation", func() { }) Context("GCThresholdPercent", func() { It("should succeed on a valid imageGCHighThresholdPercent", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ ImageGCHighThresholdPercent: ptr.Int32(10), } Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail when imageGCHighThresholdPercent is less than imageGCLowThresholdPercent", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ ImageGCHighThresholdPercent: ptr.Int32(50), ImageGCLowThresholdPercent: ptr.Int32(60), } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when imageGCLowThresholdPercent is greather than imageGCHighThresheldPercent", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ ImageGCHighThresholdPercent: ptr.Int32(50), ImageGCLowThresholdPercent: ptr.Int32(60), } @@ -629,7 +629,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("Eviction Soft Grace Period", func() { It("should succeed on evictionSoftGracePeriod with valid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoft: map[string]string{ "memory.available": "5%", "nodefs.available": "10%", @@ -650,7 +650,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail on evictionSoftGracePeriod with invalid keys", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoftGracePeriod: map[string]metav1.Duration{ "memory": {Duration: time.Minute}, }, @@ -658,7 +658,7 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail when eviction soft grace period doesn't have matching threshold", func() { - nc.Spec.Kubelet = &corev1.KubeletConfiguration{ + nc.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionSoftGracePeriod: map[string]metav1.Duration{ "memory.available": {Duration: time.Minute}, }, @@ -669,7 +669,7 @@ var _ = Describe("CEL/Validation", func() { }) Context("MetadataOptions", func() { It("should succeed for valid inputs", func() { - nc.Spec.MetadataOptions = &corev1.MetadataOptions{ + nc.Spec.MetadataOptions = &providerv1.MetadataOptions{ HTTPEndpoint: aws.String("disabled"), HTTPProtocolIPv6: aws.String("enabled"), HTTPPutResponseHopLimit: aws.Int64(34), @@ -678,25 +678,25 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nc)).To(Succeed()) }) It("should fail for invalid for HTTPEndpoint", func() { - nc.Spec.MetadataOptions = &corev1.MetadataOptions{ + nc.Spec.MetadataOptions = &providerv1.MetadataOptions{ HTTPEndpoint: aws.String("test"), } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail for invalid for HTTPProtocolIPv6", func() { - nc.Spec.MetadataOptions = &corev1.MetadataOptions{ + nc.Spec.MetadataOptions = &providerv1.MetadataOptions{ HTTPProtocolIPv6: aws.String("test"), } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail for invalid for HTTPPutResponseHopLimit", func() { - nc.Spec.MetadataOptions = &corev1.MetadataOptions{ + nc.Spec.MetadataOptions = &providerv1.MetadataOptions{ HTTPPutResponseHopLimit: aws.Int64(-5), } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) }) It("should fail for invalid for HTTPTokens", func() { - nc.Spec.MetadataOptions = &corev1.MetadataOptions{ + nc.Spec.MetadataOptions = &providerv1.MetadataOptions{ HTTPTokens: aws.String("test"), } Expect(env.Client.Create(ctx, nc)).ToNot(Succeed()) @@ -704,17 +704,17 @@ var _ = Describe("CEL/Validation", func() { }) Context("BlockDeviceMappings", func() { It("should succeed if more than one root volume is specified", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(500, resource.Giga), }, @@ -722,7 +722,7 @@ var _ = Describe("CEL/Validation", func() { }, { DeviceName: aws.String("map-device-2"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(50, resource.Tera), }, @@ -734,17 +734,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Succeed()) }) It("should succeed for valid VolumeSize in G", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(58, resource.Giga), }, RootVolume: false, @@ -755,17 +755,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Succeed()) }) It("should succeed for valid VolumeSize in T", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(45, resource.Tera), }, RootVolume: false, @@ -776,24 +776,24 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Succeed()) }) It("should fail if more than one root volume is specified", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(50, resource.Giga), }, RootVolume: true, }, { DeviceName: aws.String("map-device-2"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(50, resource.Giga), }, RootVolume: true, @@ -804,17 +804,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Not(Succeed())) }) It("should fail VolumeSize is less then 1Gi/1G", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(1, resource.Milli), }, RootVolume: false, @@ -825,17 +825,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Not(Succeed())) }) It("should fail VolumeSize is greater then 64T", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: resource.NewScaledQuantity(100, resource.Tera), }, RootVolume: false, @@ -846,17 +846,17 @@ var _ = Describe("CEL/Validation", func() { Expect(env.Client.Create(ctx, nodeClass)).To(Not(Succeed())) }) It("should fail for VolumeSize that do not parse into quantity values", func() { - nodeClass := &corev1.EC2NodeClass{ + nodeClass := &providerv1.EC2NodeClass{ ObjectMeta: coretest.ObjectMeta(metav1.ObjectMeta{}), - Spec: corev1.EC2NodeClassSpec{ + Spec: providerv1.EC2NodeClassSpec{ AMIFamily: nc.Spec.AMIFamily, SubnetSelectorTerms: nc.Spec.SubnetSelectorTerms, SecurityGroupSelectorTerms: nc.Spec.SecurityGroupSelectorTerms, Role: nc.Spec.Role, - BlockDeviceMappings: []*corev1.BlockDeviceMapping{ + BlockDeviceMappings: []*providerv1.BlockDeviceMapping{ { DeviceName: aws.String("map-device-1"), - EBS: &corev1.BlockDevice{ + EBS: &providerv1.BlockDevice{ VolumeSize: &resource.Quantity{}, }, RootVolume: false, diff --git a/pkg/apis/v1beta1/ec2nodeclass.go b/pkg/apis/v1beta1/ec2nodeclass.go index b3395dd3a60d..58b0b67acc34 100644 --- a/pkg/apis/v1beta1/ec2nodeclass.go +++ b/pkg/apis/v1beta1/ec2nodeclass.go @@ -318,7 +318,6 @@ const ( // EC2NodeClass is the Schema for the EC2NodeClass API // +kubebuilder:object:root=true -// +kubebuilder:storageversion // +kubebuilder:resource:path=ec2nodeclasses,scope=Cluster,categories=karpenter,shortName={ec2nc,ec2ncs} // +kubebuilder:subresource:status type EC2NodeClass struct { diff --git a/pkg/apis/v1beta1/ec2nodeclass_conversion.go b/pkg/apis/v1beta1/ec2nodeclass_conversion.go new file mode 100644 index 000000000000..b10d91cace27 --- /dev/null +++ b/pkg/apis/v1beta1/ec2nodeclass_conversion.go @@ -0,0 +1,27 @@ +/* +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 v1beta1 + +import ( + "context" + + "knative.dev/pkg/apis" +) + +// Since v1 is the hub conversion version, We will only need to implement conversion webhooks for v1 + +func (in *EC2NodeClass) ConvertTo(ctx context.Context, to apis.Convertible) error { return nil } + +func (in *EC2NodeClass) ConvertFrom(ctx context.Context, from apis.Convertible) error { return nil } diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index e2edbd152410..a884b0461308 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -172,7 +172,11 @@ func (c *CloudProvider) GetInstanceTypes(ctx context.Context, nodePool *corev1.N return nil, fmt.Errorf("resolving node class, %w", err) } // TODO, break this coupling - instanceTypes, err := c.instanceTypeProvider.List(ctx, nodeClass.Spec.Kubelet, nodeClass) + v1kubelet, err := utils.GetKubelet(nodePool.Annotations[corev1.ProviderCompatabilityAnnotationKey], nodeClass) + if err != nil { + return nil, err + } + instanceTypes, err := c.instanceTypeProvider.List(ctx, v1kubelet, nodeClass) if err != nil { return nil, err } @@ -252,7 +256,11 @@ func (c *CloudProvider) resolveNodeClassFromNodePool(ctx context.Context, nodePo } func (c *CloudProvider) resolveInstanceTypes(ctx context.Context, nodeClaim *corev1.NodeClaim, nodeClass *providerv1.EC2NodeClass) ([]*cloudprovider.InstanceType, error) { - instanceTypes, err := c.instanceTypeProvider.List(ctx, nodeClass.Spec.Kubelet, nodeClass) + v1kubelet, err := utils.GetKubelet(nodeClaim.Annotations[corev1.ProviderCompatabilityAnnotationKey], nodeClass) + if err != nil { + return nil, err + } + instanceTypes, err := c.instanceTypeProvider.List(ctx, v1kubelet, nodeClass) if err != nil { return nil, fmt.Errorf("getting instance types, %w", err) } diff --git a/pkg/cloudprovider/suite_test.go b/pkg/cloudprovider/suite_test.go index d72bc8709909..07f28f91051f 100644 --- a/pkg/cloudprovider/suite_test.go +++ b/pkg/cloudprovider/suite_test.go @@ -1118,7 +1118,7 @@ var _ = Describe("CloudProvider", func() { createFleetInput := awsEnv.EC2API.CreateFleetBehavior.CalledWithInput.Pop() Expect(fake.SubnetsFromFleetRequest(createFleetInput)).To(ConsistOf("test-subnet-2")) }) - FIt("should launch instances into subnet with the most available IP addresses in-between cache refreshes", func() { + It("should launch instances into subnet with the most available IP addresses in-between cache refreshes", func() { awsEnv.SubnetCache.Flush() awsEnv.EC2API.DescribeSubnetsOutput.Set(&ec2.DescribeSubnetsOutput{Subnets: []*ec2.Subnet{ {SubnetId: aws.String("test-subnet-1"), AvailabilityZone: aws.String("test-zone-1a"), AvailabilityZoneId: aws.String("tstz1-1a"), AvailableIpAddressCount: aws.Int64(10), diff --git a/pkg/providers/instancetype/instancetype.go b/pkg/providers/instancetype/instancetype.go index 0dc21de0f0a5..e9b27c504681 100644 --- a/pkg/providers/instancetype/instancetype.go +++ b/pkg/providers/instancetype/instancetype.go @@ -122,7 +122,7 @@ func (p *DefaultProvider) List(ctx context.Context, kc *providerv1.KubeletConfig // Compute fully initialized instance types hash key subnetZonesHash, _ := hashstructure.Hash(subnetZones, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true}) - kcHash, _ := hashstructure.Hash(kc, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true}) + kcHash, _ := hashstructure.Hash(nodeClass.Spec.Kubelet, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true}) blockDeviceMappingsHash, _ := hashstructure.Hash(nodeClass.Spec.BlockDeviceMappings, hashstructure.FormatV2, &hashstructure.HashOptions{SlicesAsSets: true}) key := fmt.Sprintf("%d-%d-%d-%016x-%016x-%016x-%s-%s", p.instanceTypesSeqNum, diff --git a/pkg/providers/instancetype/suite_test.go b/pkg/providers/instancetype/suite_test.go index 8d56c246b720..4139efb527d7 100644 --- a/pkg/providers/instancetype/suite_test.go +++ b/pkg/providers/instancetype/suite_test.go @@ -35,6 +35,7 @@ import ( "github.com/samber/lo" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/record" clock "k8s.io/utils/clock/testing" @@ -2321,14 +2322,22 @@ var _ = Describe("InstanceTypeProvider", func() { SystemReserved: map[string]string{string(v1.ResourceCPU): "1"}, EvictionHard: map[string]string{"memory.available": "5%"}, EvictionSoft: map[string]string{"nodefs.available": "10%"}, - MaxPods: aws.Int32(10), + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "nodefs.available": metav1.Duration{Duration: time.Minute}, + }, + MaxPods: aws.Int32(10), } kubeletChanges := []*providerv1.KubeletConfiguration{ {}, // Testing the base case black EC2NodeClass {KubeReserved: map[string]string{string(v1.ResourceCPU): "20"}}, {SystemReserved: map[string]string{string(v1.ResourceMemory): "10Gi"}}, {EvictionHard: map[string]string{"memory.available": "52%"}}, - {EvictionSoft: map[string]string{"nodefs.available": "132%"}}, + { + EvictionSoft: map[string]string{"nodefs.available": "132%"}, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "nodefs.available": metav1.Duration{Duration: time.Minute}, + }, + }, {MaxPods: aws.Int32(20)}, } var instanceTypeResult [][]*corecloudprovider.InstanceType @@ -2444,7 +2453,7 @@ var _ = Describe("InstanceTypeProvider", func() { go func() { defer wg.Done() defer GinkgoRecover() - instanceTypes, err := awsEnv.InstanceTypesProvider.List(ctx, &providerv1.KubeletConfiguration{}, nodeClass) + instanceTypes, err := awsEnv.InstanceTypesProvider.List(ctx, nodeClass.Spec.Kubelet, nodeClass) Expect(err).ToNot(HaveOccurred()) // Sort everything in parallel and ensure that we don't get data races diff --git a/pkg/providers/launchtemplate/suite_test.go b/pkg/providers/launchtemplate/suite_test.go index 03be7764db23..c2067f2230c7 100644 --- a/pkg/providers/launchtemplate/suite_test.go +++ b/pkg/providers/launchtemplate/suite_test.go @@ -216,8 +216,8 @@ var _ = Describe("LaunchTemplate Provider", func() { }, NodeClassRef: &corev1.NodeClassReference{ Group: object.GVK(nodeClass2).Group, - Kind: object.GVK(nodeClass2).Kind, - Name: nodeClass2.Name, + Kind: object.GVK(nodeClass2).Kind, + Name: nodeClass2.Name, }, }, }, @@ -1423,7 +1423,6 @@ var _ = Describe("LaunchTemplate Provider", func() { ExpectNotScheduled(ctx, env.Client, pod) }) It("should override system reserved values in user data", func() { - ExpectApplied(ctx, env.Client, nodeClass) nodeClass.Spec.Kubelet = &providerv1.KubeletConfiguration{ SystemReserved: map[string]string{ string(v1.ResourceCPU): "2", @@ -1431,7 +1430,7 @@ var _ = Describe("LaunchTemplate Provider", func() { string(v1.ResourceEphemeralStorage): "10Gi", }, } - ExpectApplied(ctx, env.Client, nodePool) + ExpectApplied(ctx, env.Client, nodePool, nodeClass) pod := coretest.UnschedulablePod() ExpectProvisioned(ctx, env.Client, cluster, cloudProvider, prov, pod) ExpectScheduled(ctx, env.Client, pod) @@ -1448,7 +1447,6 @@ var _ = Describe("LaunchTemplate Provider", func() { }) }) It("should override kube reserved values in user data", func() { - ExpectApplied(ctx, env.Client, nodeClass) nodeClass.Spec.Kubelet = &providerv1.KubeletConfiguration{ KubeReserved: map[string]string{ string(v1.ResourceCPU): "2", @@ -1456,7 +1454,7 @@ var _ = Describe("LaunchTemplate Provider", func() { string(v1.ResourceEphemeralStorage): "10Gi", }, } - ExpectApplied(ctx, env.Client, nodePool) + ExpectApplied(ctx, env.Client, nodePool, nodeClass) pod := coretest.UnschedulablePod() ExpectProvisioned(ctx, env.Client, cluster, cloudProvider, prov, pod) ExpectScheduled(ctx, env.Client, pod) @@ -1473,7 +1471,6 @@ var _ = Describe("LaunchTemplate Provider", func() { }) }) It("should override kube reserved values in user data", func() { - ExpectApplied(ctx, env.Client, nodeClass) nodeClass.Spec.Kubelet = &providerv1.KubeletConfiguration{ EvictionHard: map[string]string{ "memory.available": "10%", @@ -1481,7 +1478,7 @@ var _ = Describe("LaunchTemplate Provider", func() { "nodefs.inodesFree": "5%", }, } - ExpectApplied(ctx, env.Client, nodePool) + ExpectApplied(ctx, env.Client, nodePool, nodeClass) pod := coretest.UnschedulablePod() ExpectProvisioned(ctx, env.Client, cluster, cloudProvider, prov, pod) ExpectScheduled(ctx, env.Client, pod) diff --git a/pkg/utils/suite_test.go b/pkg/utils/suite_test.go new file mode 100644 index 000000000000..ea28a19e40cc --- /dev/null +++ b/pkg/utils/suite_test.go @@ -0,0 +1,111 @@ +/* +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 utils_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" + "github.com/aws/karpenter-provider-aws/pkg/test" + "github.com/aws/karpenter-provider-aws/pkg/utils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/samber/lo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + corev1 "sigs.k8s.io/karpenter/pkg/apis/v1" + coretest "sigs.k8s.io/karpenter/pkg/test" + + . "sigs.k8s.io/karpenter/pkg/utils/testing" +) + +var ctx context.Context +var np *corev1.NodePool +var nc *v1.EC2NodeClass + +func TestAWS(t *testing.T) { + ctx = TestContextWithLogger(t) + RegisterFailHandler(Fail) + RunSpecs(t, "Utils ") +} + +var _ = BeforeEach(func() { + nc = test.EC2NodeClass() + np = coretest.NodePool() + +}) + +var _ = Describe("GetKubelet", func() { + It("use v1beta1 NodePool kubelet configuration when defined by the compatibility annotation", func() { + nc.Spec.Kubelet = &v1.KubeletConfiguration{ + MaxPods: lo.ToPtr(int32(343)), + PodsPerCore: lo.ToPtr(int32(243)), + EvictionHard: map[string]string{ + "test-key-1": "test-value-2", + }, + EvictionSoft: map[string]string{ + "test-key-1": "test-value-2", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "test-key-1": metav1.Duration{Duration: time.Minute}, + }, + EvictionMaxPodGracePeriod: lo.ToPtr(int32(43412)), + ClusterDNS: []string{"test-dns"}, + ImageGCHighThresholdPercent: lo.ToPtr(int32(2323)), + ImageGCLowThresholdPercent: lo.ToPtr(int32(23)), + CPUCFSQuota: lo.ToPtr(true), + } + npkubelet := &v1.KubeletConfiguration{ + MaxPods: lo.ToPtr(int32(332213)), + } + kubeletbyte, err := json.Marshal(npkubelet) + Expect(err).To(BeNil()) + np.Annotations = map[string]string{ + corev1.ProviderCompatabilityAnnotationKey: string(kubeletbyte), + } + actualKubelet, err := utils.GetKubelet(np.Annotations[corev1.ProviderCompatabilityAnnotationKey], nc) + Expect(err).To(BeNil()) + Expect(npkubelet).To(BeEquivalentTo(actualKubelet)) + }) + It("should use v1 EC2NodeClass kubeletconfiguration of compatibility annotation is not found", func() { + np.Annotations = map[string]string{ + corev1.ProviderCompatabilityAnnotationKey: "", + } + nc.Spec.Kubelet = &v1.KubeletConfiguration{ + MaxPods: lo.ToPtr(int32(343)), + PodsPerCore: lo.ToPtr(int32(243)), + EvictionHard: map[string]string{ + "test-key-1": "test-value-2", + }, + EvictionSoft: map[string]string{ + "test-key-1": "test-value-2", + }, + EvictionSoftGracePeriod: map[string]metav1.Duration{ + "test-key-1": metav1.Duration{Duration: time.Minute}, + }, + EvictionMaxPodGracePeriod: lo.ToPtr(int32(43412)), + ClusterDNS: []string{"test-dns"}, + ImageGCHighThresholdPercent: lo.ToPtr(int32(2323)), + ImageGCLowThresholdPercent: lo.ToPtr(int32(23)), + CPUCFSQuota: lo.ToPtr(true), + } + kubelet, err := utils.GetKubelet(np.Annotations[corev1.ProviderCompatabilityAnnotationKey], nc) + Expect(err).To(BeNil()) + Expect(nc.Spec.Kubelet).To(BeEquivalentTo(kubelet)) + }) +}) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 2dc741e87e3d..310bad372d2e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -15,8 +15,11 @@ limitations under the License. package utils import ( + "encoding/json" "fmt" + v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" "regexp" + "sigs.k8s.io/karpenter/pkg/apis/v1beta1" "strings" "github.com/aws/aws-sdk-go/aws" @@ -66,3 +69,39 @@ func PrettySlice[T any](s []T, maxItems int) string { } return sb.String() } + +// ConvertedKubelet use the most recent version of the kubelet configuration. +// The priority of fields is listed below: +// 1.) v1 NodePool kubelet annotation (Showing a user configured using v1beta1 NodePool at some point) +// 2.) v1 EC2NodeClass will be used (showing a user configured using v1 EC2NodeClass) +func GetKubelet(kubeletAnnotation string, enc *v1.EC2NodeClass) (*v1.KubeletConfiguration, error) { + if kubeletAnnotation != "" { + kubelet := &v1beta1.KubeletConfiguration{} + err := json.Unmarshal([]byte(kubeletAnnotation), kubelet) + if err != nil { + return nil, err + } + return updateKubeletType(kubelet), nil + } + + return enc.Spec.Kubelet, nil +} + +func updateKubeletType(kubelet *v1beta1.KubeletConfiguration) *v1.KubeletConfiguration { + resultKubelet := &v1.KubeletConfiguration{} + + resultKubelet.ClusterDNS = kubelet.ClusterDNS + resultKubelet.MaxPods = kubelet.MaxPods + resultKubelet.PodsPerCore = kubelet.PodsPerCore + resultKubelet.SystemReserved = kubelet.SystemReserved + resultKubelet.KubeReserved = kubelet.KubeReserved + resultKubelet.EvictionSoft = kubelet.EvictionSoft + resultKubelet.EvictionHard = kubelet.EvictionHard + resultKubelet.EvictionSoftGracePeriod = kubelet.EvictionSoftGracePeriod + resultKubelet.EvictionMaxPodGracePeriod = kubelet.EvictionMaxPodGracePeriod + resultKubelet.ImageGCHighThresholdPercent = kubelet.ImageGCHighThresholdPercent + resultKubelet.ImageGCLowThresholdPercent = kubelet.ImageGCLowThresholdPercent + resultKubelet.CPUCFSQuota = kubelet.CPUCFSQuota + + return resultKubelet +} diff --git a/pkg/webhooks/webhooks.go b/pkg/webhooks/webhooks.go index feb627d89de1..76d2d2f140b2 100644 --- a/pkg/webhooks/webhooks.go +++ b/pkg/webhooks/webhooks.go @@ -22,12 +22,29 @@ import ( "knative.dev/pkg/controller" knativeinjection "knative.dev/pkg/injection" "knative.dev/pkg/webhook/resourcesemantics" + "knative.dev/pkg/webhook/resourcesemantics/conversion" "knative.dev/pkg/webhook/resourcesemantics/defaulting" "knative.dev/pkg/webhook/resourcesemantics/validation" + v1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" + "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" "github.com/awslabs/operatorpkg/object" +) - "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" +var ( + Resources = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ + object.GVK(&v1beta1.EC2NodeClass{}): &v1beta1.EC2NodeClass{}, + } + ConversionResource = map[schema.GroupKind]conversion.GroupKindConversion{ + object.GVK(&v1.EC2NodeClass{}).GroupKind(): { + DefinitionName: "ec2nodeclasses.karpenter.k8s.aws", + HubVersion: "v1", + Zygotes: map[string]conversion.ConvertibleObject{ + "v1": &v1.EC2NodeClass{}, + "v1beta1": &v1beta1.EC2NodeClass{}, + }, + }, + } ) func NewWebhooks() []knativeinjection.ControllerConstructor { @@ -57,6 +74,10 @@ func NewCRDValidationWebhook(ctx context.Context, _ configmap.Watcher) *controll ) } -var Resources = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ - object.GVK(&v1beta1.EC2NodeClass{}): &v1beta1.EC2NodeClass{}, +func NewCRDConversionWebhook(ctx context.Context, _ configmap.Watcher) *controller.Impl { + return conversion.NewConversionController(ctx, + "/conversion/karpenter.sh", + ConversionResource, + func(ctx context.Context) context.Context { return ctx }, + ) } diff --git a/test/pkg/debug/node.go b/test/pkg/debug/node.go index 1e030f42db10..170a23362940 100644 --- a/test/pkg/debug/node.go +++ b/test/pkg/debug/node.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/karpenter/pkg/apis/v1beta1" + corev1 "sigs.k8s.io/karpenter/pkg/apis/v1" nodeutils "sigs.k8s.io/karpenter/pkg/utils/node" ) @@ -58,7 +58,7 @@ func (c *NodeController) Reconcile(ctx context.Context, req reconcile.Request) ( func (c *NodeController) GetInfo(ctx context.Context, n *v1.Node) string { pods, _ := nodeutils.GetPods(ctx, c.kubeClient, n) - return fmt.Sprintf("ready=%s schedulable=%t initialized=%s pods=%d taints=%v", nodeutils.GetCondition(n, v1.NodeReady).Status, !n.Spec.Unschedulable, n.Labels[v1beta1.NodeInitializedLabelKey], len(pods), n.Spec.Taints) + return fmt.Sprintf("ready=%s schedulable=%t initialized=%s pods=%d taints=%v", nodeutils.GetCondition(n, v1.NodeReady).Status, !n.Spec.Unschedulable, n.Labels[corev1.NodeInitializedLabelKey], len(pods), n.Spec.Taints) } func (c *NodeController) Register(ctx context.Context, m manager.Manager) error { @@ -74,7 +74,7 @@ func (c *NodeController) Register(ctx context.Context, m manager.Manager) error }, }, predicate.NewPredicateFuncs(func(o client.Object) bool { - return o.GetLabels()[v1beta1.NodePoolLabelKey] != "" + return o.GetLabels()[corev1.NodePoolLabelKey] != "" }), )). WithOptions(controller.Options{MaxConcurrentReconciles: 10}). diff --git a/test/suites/integration/launch_template_test.go b/test/suites/integration/launch_template_test.go index 41f602105ad7..c39991a00448 100644 --- a/test/suites/integration/launch_template_test.go +++ b/test/suites/integration/launch_template_test.go @@ -21,7 +21,7 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" coretest "sigs.k8s.io/karpenter/pkg/test" - "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" + providerv1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -38,7 +38,7 @@ var _ = Describe("Launch Template Deletion", func() { Eventually(func(g Gomega) { output, _ := env.EC2API.DescribeLaunchTemplatesWithContext(env.Context, &ec2.DescribeLaunchTemplatesInput{ Filters: []*ec2.Filter{ - {Name: aws.String(fmt.Sprintf("tag:%s", v1beta1.LabelNodeClass)), Values: []*string{aws.String(nodeClass.Name)}}, + {Name: aws.String(fmt.Sprintf("tag:%s", providerv1.LabelNodeClass)), Values: []*string{aws.String(nodeClass.Name)}}, }, }) g.Expect(output.LaunchTemplates).To(HaveLen(0)) diff --git a/test/suites/integration/utilization_test.go b/test/suites/integration/utilization_test.go index 39a8bd3326aa..6ad9031617b3 100644 --- a/test/suites/integration/utilization_test.go +++ b/test/suites/integration/utilization_test.go @@ -27,7 +27,7 @@ import ( "github.com/samber/lo" - "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" + providerv1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" "github.com/aws/karpenter-provider-aws/test/pkg/debug" . "github.com/onsi/ginkgo/v2" @@ -45,7 +45,7 @@ var _ = Describe("Utilization", Label(debug.NoWatch), Label(debug.NoEvents), fun }, corev1.NodeSelectorRequirementWithMinValues{ NodeSelectorRequirement: v1.NodeSelectorRequirement{ - Key: v1beta1.LabelInstanceCategory, + Key: providerv1.LabelInstanceCategory, Operator: v1.NodeSelectorOpExists, }, }, diff --git a/test/suites/nodeclaim/garbage_collection_test.go b/test/suites/nodeclaim/garbage_collection_test.go index 4da125047cc1..6b4b5973c316 100644 --- a/test/suites/nodeclaim/garbage_collection_test.go +++ b/test/suites/nodeclaim/garbage_collection_test.go @@ -25,10 +25,10 @@ import ( "github.com/samber/lo" v1 "k8s.io/api/core/v1" - corev1 "sigs.k8s.io/karpenter/pkg/apis/v1beta1" + corev1 "sigs.k8s.io/karpenter/pkg/apis/v1" coretest "sigs.k8s.io/karpenter/pkg/test" - "github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1" + providerv1 "github.com/aws/karpenter-provider-aws/pkg/apis/v1" awserrors "github.com/aws/karpenter-provider-aws/pkg/errors" "github.com/aws/karpenter-provider-aws/pkg/utils" environmentaws "github.com/aws/karpenter-provider-aws/test/pkg/environment/aws" @@ -86,7 +86,7 @@ var _ = Describe("GarbageCollection", func() { Value: aws.String(nodePool.Name), }, { - Key: aws.String(v1beta1.LabelNodeClass), + Key: aws.String(providerv1.LabelNodeClass), Value: aws.String(nodeClass.Name), }, },