@@ -805,3 +805,223 @@ func TestComputeHash_ComprehensiveTest(t *testing.T) {
805805 g .Expect (normalized .PreemptibleInstanceConfig ).To (BeNil ())
806806 g .Expect (normalized .SourceDetails ).ToNot (BeNil ())
807807}
808+
809+ func TestComputeHash_ExtendedMetadataChangeProducesDifferentHash (t * testing.T ) {
810+ g := NewWithT (t )
811+
812+ base := & core.InstanceConfigurationLaunchInstanceDetails {
813+ Shape : common .String ("VM.Standard2.1" ),
814+ }
815+
816+ withExtMeta := & core.InstanceConfigurationLaunchInstanceDetails {
817+ Shape : common .String ("VM.Standard2.1" ),
818+ ExtendedMetadata : map [string ]interface {}{
819+ "cilium-primary-vnic" : map [string ]interface {}{
820+ "ip-count" : float64 (32 ),
821+ },
822+ },
823+ }
824+
825+ hashBase , err := ComputeHash (base )
826+ g .Expect (err ).To (BeNil ())
827+
828+ hashWithMeta , err := ComputeHash (withExtMeta )
829+ g .Expect (err ).To (BeNil ())
830+
831+ g .Expect (hashBase ).ToNot (Equal (hashWithMeta ))
832+ }
833+
834+ func TestComputeComparableHash_ExtendedMetadataKeyRemovalDetected (t * testing.T ) {
835+ g := NewWithT (t )
836+
837+ // Actual instance config still has keys {a, b}
838+ actual := & core.InstanceConfigurationLaunchInstanceDetails {
839+ Shape : common .String ("VM.Standard2.1" ),
840+ ExtendedMetadata : map [string ]interface {}{
841+ "cilium-primary-vnic" : map [string ]interface {}{
842+ "ip-count" : float64 (32 ),
843+ },
844+ "removed-key" : "old-value" ,
845+ },
846+ }
847+
848+ // Desired spec now only has {a} — user removed "removed-key"
849+ desired := & core.InstanceConfigurationLaunchInstanceDetails {
850+ Shape : common .String ("VM.Standard2.1" ),
851+ ExtendedMetadata : map [string ]interface {}{
852+ "cilium-primary-vnic" : map [string ]interface {}{
853+ "ip-count" : float64 (32 ),
854+ },
855+ },
856+ }
857+
858+ hashActual , err := ComputeComparableHash (actual , desired )
859+ g .Expect (err ).To (BeNil ())
860+
861+ hashDesired , err := ComputeHash (desired )
862+ g .Expect (err ).To (BeNil ())
863+
864+ // Hashes must differ so the key removal triggers an instance config update
865+ g .Expect (hashActual ).ToNot (Equal (hashDesired ))
866+ }
867+
868+ func TestComputeComparableHash_ExtendedMetadataTopLevelWorkloadKeyRemovalDetected (t * testing.T ) {
869+ g := NewWithT (t )
870+
871+ actual := & core.InstanceConfigurationLaunchInstanceDetails {
872+ Shape : common .String ("VM.Standard2.1" ),
873+ ExtendedMetadata : map [string ]interface {}{
874+ "workload" : map [string ]interface {}{
875+ "profile" : "standard" ,
876+ "features" : map [string ]interface {}{
877+ "hpc" : true ,
878+ "gpu" : true ,
879+ },
880+ },
881+ "network" : map [string ]interface {}{
882+ "cni" : map [string ]interface {}{
883+ "type" : "cilium" ,
884+ "mode" : "overlay" ,
885+ },
886+ },
887+ },
888+ }
889+
890+ // Desired spec keeps network metadata but removes the top-level workload key.
891+ desired := & core.InstanceConfigurationLaunchInstanceDetails {
892+ Shape : common .String ("VM.Standard2.1" ),
893+ ExtendedMetadata : map [string ]interface {}{
894+ "network" : map [string ]interface {}{
895+ "cni" : map [string ]interface {}{
896+ "type" : "cilium" ,
897+ "mode" : "overlay" ,
898+ },
899+ },
900+ },
901+ }
902+
903+ hashActual , err := ComputeComparableHash (actual , desired )
904+ g .Expect (err ).To (BeNil ())
905+
906+ hashDesired , err := ComputeHash (desired )
907+ g .Expect (err ).To (BeNil ())
908+
909+ g .Expect (hashActual ).ToNot (Equal (hashDesired ))
910+ }
911+
912+ func TestComputeComparableHash_ExtendedMetadataSameKeysMatch (t * testing.T ) {
913+ g := NewWithT (t )
914+
915+ actual := & core.InstanceConfigurationLaunchInstanceDetails {
916+ Shape : common .String ("VM.Standard2.1" ),
917+ ExtendedMetadata : map [string ]interface {}{
918+ "cilium-primary-vnic" : map [string ]interface {}{
919+ "ip-count" : float64 (32 ),
920+ },
921+ },
922+ }
923+
924+ desired := & core.InstanceConfigurationLaunchInstanceDetails {
925+ Shape : common .String ("VM.Standard2.1" ),
926+ ExtendedMetadata : map [string ]interface {}{
927+ "cilium-primary-vnic" : map [string ]interface {}{
928+ "ip-count" : float64 (32 ),
929+ },
930+ },
931+ }
932+
933+ hashActual , err := ComputeComparableHash (actual , desired )
934+ g .Expect (err ).To (BeNil ())
935+
936+ hashDesired , err := ComputeHash (desired )
937+ g .Expect (err ).To (BeNil ())
938+
939+ g .Expect (hashActual ).To (Equal (hashDesired ))
940+ }
941+
942+ func TestComputeComparableHash_ExtendedMetadataNilDesiredDetected (t * testing.T ) {
943+ g := NewWithT (t )
944+
945+ actual := & core.InstanceConfigurationLaunchInstanceDetails {
946+ Shape : common .String ("VM.Standard2.1" ),
947+ ExtendedMetadata : map [string ]interface {}{
948+ "stale-key" : "old-value" ,
949+ },
950+ }
951+
952+ desired := & core.InstanceConfigurationLaunchInstanceDetails {
953+ Shape : common .String ("VM.Standard2.1" ),
954+ ExtendedMetadata : nil ,
955+ }
956+
957+ hashActual , err := ComputeComparableHash (actual , desired )
958+ g .Expect (err ).To (BeNil ())
959+
960+ hashDesired , err := ComputeHash (desired )
961+ g .Expect (err ).To (BeNil ())
962+
963+ g .Expect (hashActual ).ToNot (Equal (hashDesired ))
964+ }
965+
966+ func TestComputeComparableHash_ExtendedMetadataClearDetected (t * testing.T ) {
967+ tests := []struct {
968+ name string
969+ desiredExtendedMeta map [string ]interface {}
970+ }{
971+ {
972+ name : "nil desired extended metadata" ,
973+ desiredExtendedMeta : nil ,
974+ },
975+ {
976+ name : "empty desired extended metadata" ,
977+ desiredExtendedMeta : map [string ]interface {}{},
978+ },
979+ }
980+
981+ for _ , tt := range tests {
982+ t .Run (tt .name , func (t * testing.T ) {
983+ g := NewWithT (t )
984+
985+ actual := & core.InstanceConfigurationLaunchInstanceDetails {
986+ Shape : common .String ("VM.Standard2.1" ),
987+ ExtendedMetadata : map [string ]interface {}{
988+ "stale-key" : "old-value" ,
989+ },
990+ }
991+
992+ desired := & core.InstanceConfigurationLaunchInstanceDetails {
993+ Shape : common .String ("VM.Standard2.1" ),
994+ ExtendedMetadata : tt .desiredExtendedMeta ,
995+ }
996+
997+ hashActual , err := ComputeComparableHash (actual , desired )
998+ g .Expect (err ).To (BeNil ())
999+
1000+ hashDesired , err := ComputeHash (desired )
1001+ g .Expect (err ).To (BeNil ())
1002+
1003+ g .Expect (hashActual ).ToNot (Equal (hashDesired ))
1004+ })
1005+ }
1006+ }
1007+
1008+ func TestComputeHash_NilExtendedMetadataDoesNotAffectHash (t * testing.T ) {
1009+ g := NewWithT (t )
1010+
1011+ withNil := & core.InstanceConfigurationLaunchInstanceDetails {
1012+ Shape : common .String ("VM.Standard2.1" ),
1013+ }
1014+
1015+ withEmpty := & core.InstanceConfigurationLaunchInstanceDetails {
1016+ Shape : common .String ("VM.Standard2.1" ),
1017+ ExtendedMetadata : map [string ]interface {}{},
1018+ }
1019+
1020+ hashNil , err := ComputeHash (withNil )
1021+ g .Expect (err ).To (BeNil ())
1022+
1023+ hashEmpty , err := ComputeHash (withEmpty )
1024+ g .Expect (err ).To (BeNil ())
1025+
1026+ g .Expect (hashNil ).To (Equal (hashEmpty ))
1027+ }
0 commit comments