diff --git a/pkg/azure/api/providerspec.go b/pkg/azure/api/providerspec.go index c5df3e30d..da50fcc02 100644 --- a/pkg/azure/api/providerspec.go +++ b/pkg/azure/api/providerspec.go @@ -116,6 +116,14 @@ type AzureVirtualMachineProperties struct { MachineSet *AzureMachineSetConfig `json:"machineSet,omitempty"` // SecurityProfile specifies the security profile to be used for the virtual machine. SecurityProfile *AzureSecurityProfile `json:"securityProfile,omitempty"` + // CapacityReservation represents the configuration for capacity reservations on Azure. + CapacityReservation *CapacityReservation `json:"capacityReservation,omitempty"` +} + +// CapacityReservation represents the configuration for capacity reservations on Azure. +type CapacityReservation struct { + // CapacityReservationGroupID is the resource ID of the capacity reservation group to use. + CapacityReservationGroupID *string `json:"capacityReservationGroupID,omitempty"` } // AzureSecurityProfile specifies the security profile to be used for the virtual machine. diff --git a/pkg/azure/api/validation/validation.go b/pkg/azure/api/validation/validation.go index 51364b2c7..5d43aa027 100644 --- a/pkg/azure/api/validation/validation.go +++ b/pkg/azure/api/validation/validation.go @@ -12,6 +12,7 @@ import ( "slices" "strings" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5" "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" @@ -128,6 +129,7 @@ func validateProperties(properties api.AzureVirtualMachineProperties, fldPath *f allErrs = append(allErrs, validateOSProfile(properties.OsProfile, fldPath.Child("osProfile"))...) // validate availability set and vmss allErrs = append(allErrs, validateAvailabilityAndScalingConfig(properties, fldPath)...) + allErrs = append(allErrs, validateCapacityReservationConfig(properties.CapacityReservation, fldPath.Child("capacityReservation"))...) allErrs = append(allErrs, validateSecurityProfile(properties.SecurityProfile, fldPath.Child("securityProfile"))...) return allErrs } @@ -294,6 +296,38 @@ func validateAvailabilityAndScalingConfig(properties api.AzureVirtualMachineProp return allErrs } +func validateCapacityReservationConfig(capacityReservationConfig *api.CapacityReservation, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + if capacityReservationConfig == nil { + return allErrs + } + + if capacityReservationGroupID := capacityReservationConfig.CapacityReservationGroupID; capacityReservationGroupID != nil { + resourceID, err := arm.ParseResourceID(*capacityReservationGroupID) + if err != nil { + allErrs = append( + allErrs, + field.Invalid( + fldPath.Child("capacityReservationGroupID"), + *capacityReservationGroupID, + fmt.Sprintf("invalid Azure resource ID: %v", err), + ), + ) + } else if resourceType := resourceID.ResourceType.Type; !strings.EqualFold(resourceType, "CapacityReservationGroups") { + allErrs = append( + allErrs, + field.Invalid( + fldPath.Child("capacityReservationGroupID"), + *capacityReservationGroupID, + fmt.Sprintf("provided resource ID must be of type 'CapacityReservationGroups', got '%s'", resourceType), + ), + ) + } + } + return allErrs +} + func validateTags(tags map[string]string, fldPath *field.Path) field.ErrorList { const ( clusterKeyPrefix = "kubernetes.io-cluster-" diff --git a/pkg/azure/api/validation/validation_test.go b/pkg/azure/api/validation/validation_test.go index 74230d4d9..2277a83cd 100644 --- a/pkg/azure/api/validation/validation_test.go +++ b/pkg/azure/api/validation/validation_test.go @@ -561,6 +561,32 @@ func TestValidateTags(t *testing.T) { )) } +func TestCapacityReservationConfig(t *testing.T) { + fldPath := field.NewPath("providerSpec.capacityReservation") + + // invalid resource group IDs should fail validation + testConfig := &api.CapacityReservation{CapacityReservationGroupID: ptr.To("Foo/bar/baz")} + g := NewWithT(t) + errList := validateCapacityReservationConfig(testConfig, fldPath) + g.Expect(len(errList)).To(Equal(1)) + g.Expect(errList).To(ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{"Type": Equal(field.ErrorTypeInvalid), "Field": Equal("providerSpec.capacityReservation.capacityReservationGroupID")})), + )) + + // IDs for other resource types should fail validation + testConfig = &api.CapacityReservation{CapacityReservationGroupID: ptr.To("/subscriptions/foo/resourceGroups/bar/providers/Microsoft.Compute/FooResource/foobar")} + errList = validateCapacityReservationConfig(testConfig, fldPath) + g.Expect(len(errList)).To(Equal(1)) + g.Expect(errList).To(ConsistOf( + PointTo(MatchFields(IgnoreExtras, Fields{"Type": Equal(field.ErrorTypeInvalid), "Field": Equal("providerSpec.capacityReservation.capacityReservationGroupID")})), + )) + + // Valid ID should work + testConfig = &api.CapacityReservation{CapacityReservationGroupID: ptr.To("/subscriptions/foo/resourceGroups/bar/providers/Microsoft.Compute/CapacityReservationGroups/foobar")} + errList = validateCapacityReservationConfig(testConfig, fldPath) + g.Expect(errList).To(BeEmpty()) +} + func createSecret(clientID, clientSecret, workloadIdentityTokenFile, subscriptionID, tenantID, userData string) *corev1.Secret { data := make(map[string][]byte, 4) if !utils.IsEmptyString(clientID) { diff --git a/pkg/azure/provider/helpers/driver.go b/pkg/azure/provider/helpers/driver.go index fd5435855..e3c508735 100644 --- a/pkg/azure/provider/helpers/driver.go +++ b/pkg/azure/provider/helpers/driver.go @@ -771,6 +771,7 @@ func createVMCreationParams(providerSpec api.AzureProviderSpec, imageRef armcomp } } } + if diskSecurityProfile := providerSpec.Properties.StorageProfile.OsDisk.ManagedDisk.SecurityProfile; diskSecurityProfile != nil { if diskSecurityProfile.SecurityEncryptionType != nil { securityEncryptionType := armcompute.SecurityEncryptionTypes(*diskSecurityProfile.SecurityEncryptionType) @@ -780,6 +781,12 @@ func createVMCreationParams(providerSpec api.AzureProviderSpec, imageRef armcomp } } + if capacityReservationConfig := providerSpec.Properties.CapacityReservation; capacityReservationConfig != nil { + vm.Properties.CapacityReservation = &armcompute.CapacityReservationProfile{ + CapacityReservationGroup: &armcompute.SubResource{ID: capacityReservationConfig.CapacityReservationGroupID}, + } + } + return vm, nil }