Skip to content

Commit

Permalink
fix: Hash volume size quantity value as a string (#5454)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-innis authored Mar 27, 2024
1 parent 5acb150 commit 9410598
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 59 deletions.
4 changes: 2 additions & 2 deletions pkg/apis/v1beta1/ec2nodeclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ type BlockDevice struct {
// +kubebuilder:validation:Pattern:="^((?:[1-9][0-9]{0,3}|[1-4][0-9]{4}|[5][0-8][0-9]{3}|59000)Gi|(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-3][0-9]{3}|64000)G|([1-9]||[1-5][0-7]|58)Ti|([1-9]||[1-5][0-9]|6[0-3]|64)T)$"
// +kubebuilder:validation:XIntOrString
// +optional
VolumeSize *resource.Quantity `json:"volumeSize,omitempty"`
VolumeSize *resource.Quantity `json:"volumeSize,omitempty" hash:"string"`
// VolumeType of the block device.
// For more information, see Amazon EBS volume types (https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html)
// in the Amazon Elastic Compute Cloud User Guide.
Expand Down Expand Up @@ -333,7 +333,7 @@ type EC2NodeClass struct {
// 1. A field changes its default value for an existing field that is already hashed
// 2. A field is added to the hash calculation with an already-set value
// 3. A field is removed from the hash calculations
const EC2NodeClassHashVersion = "v1"
const EC2NodeClassHashVersion = "v2"

func (in *EC2NodeClass) Hash() string {
return fmt.Sprint(lo.Must(hashstructure.Hash(in.Spec, hashstructure.FormatV2, &hashstructure.HashOptions{
Expand Down
139 changes: 102 additions & 37 deletions pkg/apis/v1beta1/ec2nodeclass_hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/imdario/mergo"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/api/resource"

"github.com/aws/karpenter-provider-aws/pkg/apis/v1beta1"
"github.com/aws/karpenter-provider-aws/pkg/test"
Expand All @@ -27,76 +28,137 @@ import (
)

var _ = Describe("Hash", func() {
const staticHash = "16608948681250225098"
const staticHash = "10790156025840984195"
var nodeClass *v1beta1.EC2NodeClass
BeforeEach(func() {
nodeClass = test.EC2NodeClass(v1beta1.EC2NodeClass{
Spec: v1beta1.EC2NodeClassSpec{
AMIFamily: aws.String(v1beta1.AMIFamilyAL2),
Context: aws.String("context-1"),
AMIFamily: lo.ToPtr(v1beta1.AMIFamilyAL2023),
Role: "role-1",
Tags: map[string]string{
"keyTag-1": "valueTag-1",
"keyTag-2": "valueTag-2",
},
Context: lo.ToPtr("fake-context"),
DetailedMonitoring: lo.ToPtr(false),
AssociatePublicIPAddress: lo.ToPtr(false),
MetadataOptions: &v1beta1.MetadataOptions{
HTTPEndpoint: aws.String("test-metadata-1"),
HTTPEndpoint: lo.ToPtr("disabled"),
HTTPProtocolIPv6: lo.ToPtr("disabled"),
HTTPPutResponseHopLimit: lo.ToPtr(int64(1)),
HTTPTokens: lo.ToPtr("optional"),
},
BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{
{
DeviceName: aws.String("map-device-1"),
DeviceName: lo.ToPtr("map-device-1"),
RootVolume: false,
EBS: &v1beta1.BlockDevice{
DeleteOnTermination: lo.ToPtr(false),
Encrypted: lo.ToPtr(false),
IOPS: lo.ToPtr(int64(0)),
KMSKeyID: lo.ToPtr("fakeKMSKeyID"),
SnapshotID: lo.ToPtr("fakeSnapshot"),
Throughput: lo.ToPtr(int64(0)),
VolumeSize: resource.NewScaledQuantity(2, resource.Giga),
VolumeType: lo.ToPtr("standard"),
},
},
{
DeviceName: aws.String("map-device-2"),
DeviceName: lo.ToPtr("map-device-2"),
},
},
UserData: aws.String("userdata-test-1"),
DetailedMonitoring: aws.Bool(false),
UserData: aws.String("userdata-test-1"),
},
})
})
DescribeTable(
"should match static hash",
func(hash string, changes ...v1beta1.EC2NodeClass) {
modifiedNodeClass := test.EC2NodeClass(append([]v1beta1.EC2NodeClass{*nodeClass}, changes...)...)
Expect(modifiedNodeClass.Hash()).To(Equal(hash))
"should match static hash on field value change",
func(hash string, changes v1beta1.EC2NodeClass) {
Expect(mergo.Merge(nodeClass, changes, mergo.WithOverride, mergo.WithSliceDeepCopy)).To(Succeed())
Expect(nodeClass.Hash()).To(Equal(hash))
},
Entry("Base EC2NodeClass", staticHash),
Entry("Base EC2NodeClass", staticHash, v1beta1.EC2NodeClass{}),
// Static fields, expect changed hash from base
Entry("UserData Drift", "588756456110800812", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{UserData: aws.String("userdata-test-2")}}),
Entry("Tags Drift", "2471764681523766508", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-test-3": "valueTag-test-3"}}}),
Entry("MetadataOptions Drift", "11030161632375731908", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPEndpoint: aws.String("test-metadata-2")}}}),
Entry("BlockDeviceMappings Drift", "436753305915039702", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: aws.String("map-device-test-3")}}}}),
Entry("Context Drift", "3729470655588343019", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Context: aws.String("context-2")}}),
Entry("DetailedMonitoring Drift", "17892305444040067573", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{DetailedMonitoring: aws.Bool(true)}}),
Entry("AMIFamily Drift", "9493798894326942407", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AMIFamily: aws.String(v1beta1.AMIFamilyBottlerocket)}}),
Entry("Reorder Tags", staticHash, v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-2": "valueTag-2", "keyTag-1": "valueTag-1"}}}),
Entry("Reorder BlockDeviceMapping", staticHash, v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: aws.String("map-device-2")}, {DeviceName: aws.String("map-device-1")}}}}),
Entry("UserData", "18317182711135792962", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{UserData: aws.String("userdata-test-2")}}),
Entry("Tags", "7254882043893135054", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-test-3": "valueTag-test-3"}}}),
Entry("Context", "17271601354348855032", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Context: aws.String("context-2")}}),
Entry("DetailedMonitoring", "3320998103335094348", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{DetailedMonitoring: aws.Bool(true)}}),
Entry("AMIFamily", "11029247967399146065", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AMIFamily: aws.String(v1beta1.AMIFamilyBottlerocket)}}),
Entry("InstanceStorePolicy", "15591048753403695860", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{InstanceStorePolicy: lo.ToPtr(v1beta1.InstanceStorePolicyRAID0)}}),
Entry("AssociatePublicIPAddress", "8788624850560996180", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AssociatePublicIPAddress: lo.ToPtr(true)}}),
Entry("MetadataOptions HTTPEndpoint", "12130088184516131939", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPEndpoint: lo.ToPtr("enabled")}}}),
Entry("MetadataOptions HTTPProtocolIPv6", "9851778617676567202", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPProtocolIPv6: lo.ToPtr("enabled")}}}),
Entry("MetadataOptions HTTPPutResponseHopLimit", "10114972825726256442", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPPutResponseHopLimit: lo.ToPtr(int64(10))}}}),
Entry("MetadataOptions HTTPTokens", "15328515228245883488", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPTokens: lo.ToPtr("required")}}}),
Entry("BlockDeviceMapping DeviceName", "14855383487702710824", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: lo.ToPtr("map-device-test-3")}}}}),
Entry("BlockDeviceMapping RootVolume", "9591488558660758449", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{RootVolume: true}}}}),
Entry("BlockDeviceMapping DeleteOnTermination", "2802222466202766732", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{DeleteOnTermination: lo.ToPtr(true)}}}}}),
Entry("BlockDeviceMapping Encrypted", "16743053872042184219", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{Encrypted: lo.ToPtr(true)}}}}}),
Entry("BlockDeviceMapping IOPS", "17284705682110195253", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{IOPS: lo.ToPtr(int64(10))}}}}}),
Entry("BlockDeviceMapping KMSKeyID", "9151019926310241707", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{KMSKeyID: lo.ToPtr("test")}}}}}),
Entry("BlockDeviceMapping SnapshotID", "5250341140179985875", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{SnapshotID: lo.ToPtr("test")}}}}}),
Entry("BlockDeviceMapping Throughput", "16711481758638864953", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{Throughput: lo.ToPtr(int64(10))}}}}}),
Entry("BlockDeviceMapping VolumeType", "488614640133725370", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{VolumeType: lo.ToPtr("io1")}}}}}),

// Behavior / Dynamic fields, expect same hash as base
Entry("Modified AMISelector", staticHash, v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AMISelectorTerms: []v1beta1.AMISelectorTerm{{Tags: map[string]string{"ami-test-key": "ami-test-value"}}}}}),
Entry("Modified SubnetSelector", staticHash, v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{SubnetSelectorTerms: []v1beta1.SubnetSelectorTerm{{Tags: map[string]string{"subnet-test-key": "subnet-test-value"}}}}}),
Entry("Modified SecurityGroupSelector", staticHash, v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{SecurityGroupSelectorTerms: []v1beta1.SecurityGroupSelectorTerm{{Tags: map[string]string{"security-group-test-key": "security-group-test-value"}}}}}),
)
// 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
It("should match static hash when updating blockDeviceMapping volumeSize", func() {
nodeClass.Spec.BlockDeviceMappings[0].EBS.VolumeSize = resource.NewScaledQuantity(10, resource.Giga)
Expect(nodeClass.Hash()).To(Equal("4802236799448001710"))
})
It("should match static hash for instanceProfile", func() {
nodeClass.Spec.Role = ""
nodeClass.Spec.InstanceProfile = lo.ToPtr("test-instance-profile")
Expect(nodeClass.Hash()).To(Equal("15756064858220068103"))
Expect(nodeClass.Hash()).To(Equal("7914642030762404205"))
})
It("should match static hash when reordering tags", func() {
nodeClass.Spec.Tags = map[string]string{"keyTag-2": "valueTag-2", "keyTag-1": "valueTag-1"}
Expect(nodeClass.Hash()).To(Equal(staticHash))
})
It("should match static hash when reordering blockDeviceMappings", func() {
nodeClass.Spec.BlockDeviceMappings[0], nodeClass.Spec.BlockDeviceMappings[1] = nodeClass.Spec.BlockDeviceMappings[1], nodeClass.Spec.BlockDeviceMappings[0]
Expect(nodeClass.Hash()).To(Equal(staticHash))
})
DescribeTable("should change hash when static fields are updated", func(changes v1beta1.EC2NodeClass) {
hash := nodeClass.Hash()
Expect(mergo.Merge(nodeClass, changes, mergo.WithOverride)).To(Succeed())
Expect(mergo.Merge(nodeClass, changes, mergo.WithOverride, mergo.WithSliceDeepCopy)).To(Succeed())
updatedHash := nodeClass.Hash()
Expect(hash).ToNot(Equal(updatedHash))
},
Entry("UserData Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{UserData: aws.String("userdata-test-2")}}),
Entry("Tags Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-test-3": "valueTag-test-3"}}}),
Entry("MetadataOptions Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPEndpoint: aws.String("test-metadata-2")}}}),
Entry("BlockDeviceMappings Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: aws.String("map-device-test-3")}}}}),
Entry("Context Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Context: aws.String("context-2")}}),
Entry("DetailedMonitoring Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{DetailedMonitoring: aws.Bool(true)}}),
Entry("AMIFamily Drift", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AMIFamily: aws.String(v1beta1.AMIFamilyBottlerocket)}}),
Entry("UserData", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{UserData: aws.String("userdata-test-2")}}),
Entry("Tags", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-test-3": "valueTag-test-3"}}}),
Entry("Context", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Context: aws.String("context-2")}}),
Entry("DetailedMonitoring", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{DetailedMonitoring: aws.Bool(true)}}),
Entry("AMIFamily", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AMIFamily: aws.String(v1beta1.AMIFamilyBottlerocket)}}),
Entry("InstanceStorePolicy", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{InstanceStorePolicy: lo.ToPtr(v1beta1.InstanceStorePolicyRAID0)}}),
Entry("AssociatePublicIPAddress", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{AssociatePublicIPAddress: lo.ToPtr(true)}}),
Entry("MetadataOptions HTTPEndpoint", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPEndpoint: lo.ToPtr("enabled")}}}),
Entry("MetadataOptions HTTPProtocolIPv6", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPProtocolIPv6: lo.ToPtr("enabled")}}}),
Entry("MetadataOptions HTTPPutResponseHopLimit", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPPutResponseHopLimit: lo.ToPtr(int64(10))}}}),
Entry("MetadataOptions HTTPTokens", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{MetadataOptions: &v1beta1.MetadataOptions{HTTPTokens: lo.ToPtr("required")}}}),
Entry("BlockDeviceMapping DeviceName", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: lo.ToPtr("map-device-test-3")}}}}),
Entry("BlockDeviceMapping RootVolume", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{RootVolume: true}}}}),
Entry("BlockDeviceMapping DeleteOnTermination", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{DeleteOnTermination: lo.ToPtr(true)}}}}}),
Entry("BlockDeviceMapping Encrypted", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{Encrypted: lo.ToPtr(true)}}}}}),
Entry("BlockDeviceMapping IOPS", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{IOPS: lo.ToPtr(int64(10))}}}}}),
Entry("BlockDeviceMapping KMSKeyID", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{KMSKeyID: lo.ToPtr("test")}}}}}),
Entry("BlockDeviceMapping SnapshotID", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{SnapshotID: lo.ToPtr("test")}}}}}),
Entry("BlockDeviceMapping Throughput", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{Throughput: lo.ToPtr(int64(10))}}}}}),
Entry("BlockDeviceMapping VolumeType", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{EBS: &v1beta1.BlockDevice{VolumeType: lo.ToPtr("io1")}}}}}),
)
// 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
It("should change hash blockDeviceMapping volumeSize is updated", func() {
hash := nodeClass.Hash()
nodeClass.Spec.BlockDeviceMappings[0].EBS.VolumeSize = resource.NewScaledQuantity(10, resource.Giga)
updatedHash := nodeClass.Hash()
Expect(hash).ToNot(Equal(updatedHash))
})
It("should change hash when instanceProfile is updated", func() {
nodeClass.Spec.Role = ""
nodeClass.Spec.InstanceProfile = lo.ToPtr("test-instance-profile")
Expand All @@ -105,15 +167,18 @@ var _ = Describe("Hash", func() {
updatedHash := nodeClass.Hash()
Expect(hash).ToNot(Equal(updatedHash))
})
DescribeTable("should not change hash when slices are re-ordered", func(changes v1beta1.EC2NodeClass) {
It("should not change hash when tags are re-ordered", func() {
hash := nodeClass.Hash()
Expect(mergo.Merge(nodeClass, changes, mergo.WithOverride)).To(Succeed())
nodeClass.Spec.Tags = map[string]string{"keyTag-2": "valueTag-2", "keyTag-1": "valueTag-1"}
updatedHash := nodeClass.Hash()
Expect(hash).To(Equal(updatedHash))
},
Entry("Reorder Tags", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{Tags: map[string]string{"keyTag-2": "valueTag-2", "keyTag-1": "valueTag-1"}}}),
Entry("Reorder BlockDeviceMapping", v1beta1.EC2NodeClass{Spec: v1beta1.EC2NodeClassSpec{BlockDeviceMappings: []*v1beta1.BlockDeviceMapping{{DeviceName: aws.String("map-device-2")}, {DeviceName: aws.String("map-device-1")}}}}),
)
})
It("should not change hash when blockDeviceMappings are re-ordered", func() {
hash := nodeClass.Hash()
nodeClass.Spec.BlockDeviceMappings[0], nodeClass.Spec.BlockDeviceMappings[1] = nodeClass.Spec.BlockDeviceMappings[1], nodeClass.Spec.BlockDeviceMappings[0]
updatedHash := nodeClass.Hash()
Expect(hash).To(Equal(updatedHash))
})
It("should not change hash when behavior/dynamic fields are updated", func() {
hash := nodeClass.Hash()

Expand Down
Loading

0 comments on commit 9410598

Please sign in to comment.