Skip to content

Commit d6dedf1

Browse files
authored
feat: Add OCIMachinePool extendedMetadata support (#522)
1 parent affcdba commit d6dedf1

11 files changed

Lines changed: 437 additions & 0 deletions

cloud/hash/instanceconfighash.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type comparableLaunchDetails struct {
3535
CompartmentID *string `json:"compartmentId,omitempty"`
3636
CreateVnicDetails *comparableCreateVnicDetails `json:"createVnicDetails,omitempty"`
3737
Metadata map[string]string `json:"metadata,omitempty"`
38+
ExtendedMetadata map[string]interface{} `json:"extendedMetadata,omitempty"`
3839
Shape *string `json:"shape,omitempty"`
3940
ShapeConfig *comparableShapeConfig `json:"shapeConfig,omitempty"`
4041
PlatformConfig *comparablePlatformConfig `json:"platformConfig,omitempty"`
@@ -154,6 +155,7 @@ func projectLaunchDetails(in, mask *core.InstanceConfigurationLaunchInstanceDeta
154155
CompartmentID: pickString(in.CompartmentId, mask.CompartmentId),
155156
CreateVnicDetails: projectCreateVnicDetails(in.CreateVnicDetails, mask.CreateVnicDetails),
156157
Metadata: normalizeMetadata(pickMetadata(in.Metadata, mask.Metadata)),
158+
ExtendedMetadata: pickExtendedMetadata(in.ExtendedMetadata, mask.ExtendedMetadata),
157159
Shape: pickString(in.Shape, mask.Shape),
158160
ShapeConfig: projectShapeConfig(in.ShapeConfig, mask.ShapeConfig),
159161
PlatformConfig: projectPlatformConfig(in.PlatformConfig, mask.PlatformConfig),
@@ -575,6 +577,22 @@ func pickMetadata(actual, mask map[string]string) map[string]string {
575577
return result
576578
}
577579

580+
// pickExtendedMetadata returns all actual keys whenever any actual keys exist.
581+
// Unlike regular metadata, OCI does not inject default extended metadata keys,
582+
// so returning the full actual map is safe and ensures that key removals from
583+
// the desired spec, including clearing the entire field, are detected as hash
584+
// differences.
585+
func pickExtendedMetadata(actual, mask map[string]interface{}) map[string]interface{} {
586+
if len(actual) == 0 {
587+
return nil
588+
}
589+
result := make(map[string]interface{}, len(actual))
590+
for k, v := range actual {
591+
result[k] = v
592+
}
593+
return result
594+
}
595+
578596
func pickStrings(actual, mask []string) []string {
579597
if len(mask) == 0 {
580598
return nil

cloud/hash/instanceconfighash_test.go

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

cloud/scope/machine_pool.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,11 +655,17 @@ func (m *MachinePoolScope) getLaunchInstanceDetails(instanceConfigurationSpec in
655655
}
656656
metadata["user_data"] = base64.StdEncoding.EncodeToString([]byte(cloudInitData))
657657

658+
extendedMetadata, err := ConvertMachineExtendedMetadata(instanceConfigurationSpec.ExtendedMetadata)
659+
if err != nil {
660+
return nil, err
661+
}
662+
658663
launchDetails := &core.InstanceConfigurationLaunchInstanceDetails{
659664
CompartmentId: common.String(m.OCIClusterAccesor.GetCompartmentId()),
660665
DisplayName: common.String(m.OCIMachinePool.GetName()),
661666
Shape: common.String(*m.OCIMachinePool.Spec.InstanceConfiguration.Shape),
662667
Metadata: metadata,
668+
ExtendedMetadata: extendedMetadata,
663669
DedicatedVmHostId: instanceConfigurationSpec.DedicatedVmHostId,
664670
FreeformTags: freeFormTags,
665671
DefinedTags: definedTags,

0 commit comments

Comments
 (0)