Skip to content

Commit f80069e

Browse files
committed
Add support for capacity reservation
Signed-off-by: arkadeepsen <[email protected]>
1 parent 90a08dd commit f80069e

11 files changed

+285
-49
lines changed

api/v1beta1/azuremachine_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ type AzureMachineSpec struct {
142142
// The primary interface will be the first networkInterface specified (index 0) in the list.
143143
// +optional
144144
NetworkInterfaces []NetworkInterface `json:"networkInterfaces,omitempty"`
145+
146+
// CapacityReservationGroupID specifies the capacity reservation group resource id that should be
147+
// used for allocating the virtual machine.
148+
// +optional
149+
CapacityReservationGroupID *string `json:"capacityReservationGroupID,omitempty"`
145150
}
146151

147152
// SpotVMOptions defines the options relevant to running the Machine on Spot VMs.

api/v1beta1/azuremachine_validation.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func ValidateAzureMachineSpec(spec AzureMachineSpec) field.ErrorList {
6767
allErrs = append(allErrs, errs...)
6868
}
6969

70+
if errs := ValidateCapacityReservationGroupID(spec.CapacityReservationGroupID, field.NewPath("capacityReservationGroupID")); len(errs) > 0 {
71+
allErrs = append(allErrs, errs...)
72+
}
73+
7074
return allErrs
7175
}
7276

@@ -454,3 +458,16 @@ func ValidateConfidentialCompute(managedDisk *ManagedDiskParameters, profile *Se
454458

455459
return allErrs
456460
}
461+
462+
// ValidateCapacityReservationGroupID validates the capacity reservation group id.
463+
func ValidateCapacityReservationGroupID(capacityReservationGroupID *string, fldPath *field.Path) field.ErrorList {
464+
allErrs := field.ErrorList{}
465+
466+
if capacityReservationGroupID != nil {
467+
if _, err := azureutil.ParseResourceID(*capacityReservationGroupID); err != nil {
468+
allErrs = append(allErrs, field.Invalid(fldPath, capacityReservationGroupID, "must be a valid Azure resource ID"))
469+
}
470+
}
471+
472+
return allErrs
473+
}

api/v1beta1/azuremachine_webhook.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ func (mw *azureMachineWebhook) ValidateUpdate(ctx context.Context, oldObj, newOb
206206
}
207207
}
208208

209+
if err := webhookutils.ValidateImmutable(
210+
field.NewPath("spec", "capacityReservationGroupID"),
211+
old.Spec.CapacityReservationGroupID,
212+
m.Spec.CapacityReservationGroupID); err != nil {
213+
allErrs = append(allErrs, err)
214+
}
215+
209216
if len(allErrs) == 0 {
210217
return nil, nil
211218
}

api/v1beta1/azuremachine_webhook_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,21 @@ func TestAzureMachine_ValidateCreate(t *testing.T) {
215215
machine: createMachineWithConfidentialCompute(SecurityEncryptionTypeDiskWithVMGuestState, "", false, true, false),
216216
wantErr: true,
217217
},
218+
{
219+
name: "azuremachine with empty capacity reservation group id",
220+
machine: createMachineWithCapacityReservaionGroupID(""),
221+
wantErr: false,
222+
},
223+
{
224+
name: "azuremachine with valid capacity reservation group id",
225+
machine: createMachineWithCapacityReservaionGroupID("azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group/providers/Microsoft.Compute/capacityReservationGroups/capacity-reservation-group-name"),
226+
wantErr: false,
227+
},
228+
{
229+
name: "azuremachine with invalid capacity reservation group id",
230+
machine: createMachineWithCapacityReservaionGroupID("invalid-capacity-group-id"),
231+
wantErr: true,
232+
},
218233
}
219234
for _, tc := range tests {
220235
t.Run(tc.name, func(t *testing.T) {
@@ -805,6 +820,62 @@ func TestAzureMachine_ValidateUpdate(t *testing.T) {
805820
},
806821
wantErr: true,
807822
},
823+
{
824+
name: "invalidTest: azuremachine.spec.capacityReservationGroupID is immutable",
825+
oldMachine: &AzureMachine{
826+
Spec: AzureMachineSpec{
827+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"),
828+
},
829+
},
830+
newMachine: &AzureMachine{
831+
Spec: AzureMachineSpec{
832+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-2"),
833+
},
834+
},
835+
wantErr: true,
836+
},
837+
{
838+
name: "invalidTest: updating azuremachine.spec.capacityReservationGroupID from empty to non-empty",
839+
oldMachine: &AzureMachine{
840+
Spec: AzureMachineSpec{
841+
CapacityReservationGroupID: nil,
842+
},
843+
},
844+
newMachine: &AzureMachine{
845+
Spec: AzureMachineSpec{
846+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"),
847+
},
848+
},
849+
wantErr: true,
850+
},
851+
{
852+
name: "invalidTest: updating azuremachine.spec.capacityReservationGroupID from non-empty to empty",
853+
oldMachine: &AzureMachine{
854+
Spec: AzureMachineSpec{
855+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"),
856+
},
857+
},
858+
newMachine: &AzureMachine{
859+
Spec: AzureMachineSpec{
860+
CapacityReservationGroupID: nil,
861+
},
862+
},
863+
wantErr: true,
864+
},
865+
{
866+
name: "validTest: azuremachine.spec.capacityReservationGroupID is immutable",
867+
oldMachine: &AzureMachine{
868+
Spec: AzureMachineSpec{
869+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"),
870+
},
871+
},
872+
newMachine: &AzureMachine{
873+
Spec: AzureMachineSpec{
874+
CapacityReservationGroupID: ptr.To("capacityReservationGroupID-1"),
875+
},
876+
},
877+
wantErr: false,
878+
},
808879
}
809880

810881
for _, tc := range tests {
@@ -1068,3 +1139,18 @@ func createMachineWithConfidentialCompute(securityEncryptionType SecurityEncrypt
10681139
},
10691140
}
10701141
}
1142+
1143+
func createMachineWithCapacityReservaionGroupID(capacityReservationGroupID string) *AzureMachine {
1144+
var strPtr *string
1145+
if capacityReservationGroupID != "" {
1146+
strPtr = ptr.To(capacityReservationGroupID)
1147+
}
1148+
1149+
return &AzureMachine{
1150+
Spec: AzureMachineSpec{
1151+
SSHPublicKey: validSSHPublicKey,
1152+
OSDisk: validOSDisk,
1153+
CapacityReservationGroupID: strPtr,
1154+
},
1155+
}
1156+
}

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

azure/scope/machine.go

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -160,27 +160,28 @@ func (m *MachineScope) InitMachineCache(ctx context.Context) error {
160160
// VMSpec returns the VM spec.
161161
func (m *MachineScope) VMSpec() azure.ResourceSpecGetter {
162162
spec := &virtualmachines.VMSpec{
163-
Name: m.Name(),
164-
Location: m.Location(),
165-
ExtendedLocation: m.ExtendedLocation(),
166-
ResourceGroup: m.NodeResourceGroup(),
167-
ClusterName: m.ClusterName(),
168-
Role: m.Role(),
169-
NICIDs: m.NICIDs(),
170-
SSHKeyData: m.AzureMachine.Spec.SSHPublicKey,
171-
Size: m.AzureMachine.Spec.VMSize,
172-
OSDisk: m.AzureMachine.Spec.OSDisk,
173-
DataDisks: m.AzureMachine.Spec.DataDisks,
174-
AvailabilitySetID: m.AvailabilitySetID(),
175-
Zone: m.AvailabilityZone(),
176-
Identity: m.AzureMachine.Spec.Identity,
177-
UserAssignedIdentities: m.AzureMachine.Spec.UserAssignedIdentities,
178-
SpotVMOptions: m.AzureMachine.Spec.SpotVMOptions,
179-
SecurityProfile: m.AzureMachine.Spec.SecurityProfile,
180-
DiagnosticsProfile: m.AzureMachine.Spec.Diagnostics,
181-
AdditionalTags: m.AdditionalTags(),
182-
AdditionalCapabilities: m.AzureMachine.Spec.AdditionalCapabilities,
183-
ProviderID: m.ProviderID(),
163+
Name: m.Name(),
164+
Location: m.Location(),
165+
ExtendedLocation: m.ExtendedLocation(),
166+
ResourceGroup: m.NodeResourceGroup(),
167+
ClusterName: m.ClusterName(),
168+
Role: m.Role(),
169+
NICIDs: m.NICIDs(),
170+
SSHKeyData: m.AzureMachine.Spec.SSHPublicKey,
171+
Size: m.AzureMachine.Spec.VMSize,
172+
OSDisk: m.AzureMachine.Spec.OSDisk,
173+
DataDisks: m.AzureMachine.Spec.DataDisks,
174+
AvailabilitySetID: m.AvailabilitySetID(),
175+
Zone: m.AvailabilityZone(),
176+
Identity: m.AzureMachine.Spec.Identity,
177+
UserAssignedIdentities: m.AzureMachine.Spec.UserAssignedIdentities,
178+
SpotVMOptions: m.AzureMachine.Spec.SpotVMOptions,
179+
SecurityProfile: m.AzureMachine.Spec.SecurityProfile,
180+
DiagnosticsProfile: m.AzureMachine.Spec.Diagnostics,
181+
AdditionalTags: m.AdditionalTags(),
182+
AdditionalCapabilities: m.AzureMachine.Spec.AdditionalCapabilities,
183+
CapacityReservationGroupID: m.GetCapacityReservationGroupID(),
184+
ProviderID: m.ProviderID(),
184185
}
185186
if m.cache != nil {
186187
spec.SKU = m.cache.VMSKU
@@ -805,3 +806,9 @@ func (m *MachineScope) UpdatePatchStatus(condition clusterv1.ConditionType, serv
805806
conditions.MarkFalse(m.AzureMachine, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error())
806807
}
807808
}
809+
810+
// GetCapacityReservationGroupID returns the CapacityReservationGroupID from the spec if the
811+
// value is assigned, or else returns an empty string.
812+
func (m *MachineScope) GetCapacityReservationGroupID() string {
813+
return ptr.Deref(m.AzureMachine.Spec.CapacityReservationGroupID, "")
814+
}

azure/scope/machine_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3066,3 +3066,48 @@ func TestDiskSpecs(t *testing.T) {
30663066
})
30673067
}
30683068
}
3069+
3070+
func TestMachineScope_GetCapacityReservationGroupID(t *testing.T) {
3071+
tests := []struct {
3072+
name string
3073+
machineScope MachineScope
3074+
want string
3075+
}{
3076+
{
3077+
name: "returns the entire capacity reservation group ID",
3078+
machineScope: MachineScope{
3079+
AzureMachine: &infrav1.AzureMachine{
3080+
ObjectMeta: metav1.ObjectMeta{
3081+
Name: "machine-name",
3082+
},
3083+
Spec: infrav1.AzureMachineSpec{
3084+
CapacityReservationGroupID: ptr.To("azure:///subscriptions/1234-5678/resourceGroups/my-cluster/providers/Microsoft.Compute/capacityReservationGroups/capacity-reservation-group-name"),
3085+
},
3086+
},
3087+
},
3088+
want: "azure:///subscriptions/1234-5678/resourceGroups/my-cluster/providers/Microsoft.Compute/capacityReservationGroups/capacity-reservation-group-name",
3089+
},
3090+
{
3091+
name: "returns empty if capacity reservation group ID is empty",
3092+
machineScope: MachineScope{
3093+
AzureMachine: &infrav1.AzureMachine{
3094+
ObjectMeta: metav1.ObjectMeta{
3095+
Name: "machine-name",
3096+
},
3097+
Spec: infrav1.AzureMachineSpec{
3098+
CapacityReservationGroupID: ptr.To(""),
3099+
},
3100+
},
3101+
},
3102+
want: "",
3103+
},
3104+
}
3105+
for _, tt := range tests {
3106+
t.Run(tt.name, func(t *testing.T) {
3107+
got := tt.machineScope.GetCapacityReservationGroupID()
3108+
if got != tt.want {
3109+
t.Errorf("MachineScope.GetCapacityReservationGroupID() = %v, want %v", got, tt.want)
3110+
}
3111+
})
3112+
}
3113+
}

azure/services/virtualmachines/spec.go

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,31 @@ import (
3333

3434
// VMSpec defines the specification for a Virtual Machine.
3535
type VMSpec struct {
36-
Name string
37-
ResourceGroup string
38-
Location string
39-
ExtendedLocation *infrav1.ExtendedLocationSpec
40-
ClusterName string
41-
Role string
42-
NICIDs []string
43-
SSHKeyData string
44-
Size string
45-
AvailabilitySetID string
46-
Zone string
47-
Identity infrav1.VMIdentity
48-
OSDisk infrav1.OSDisk
49-
DataDisks []infrav1.DataDisk
50-
UserAssignedIdentities []infrav1.UserAssignedIdentity
51-
SpotVMOptions *infrav1.SpotVMOptions
52-
SecurityProfile *infrav1.SecurityProfile
53-
AdditionalTags infrav1.Tags
54-
AdditionalCapabilities *infrav1.AdditionalCapabilities
55-
DiagnosticsProfile *infrav1.Diagnostics
56-
SKU resourceskus.SKU
57-
Image *infrav1.Image
58-
BootstrapData string
59-
ProviderID string
36+
Name string
37+
ResourceGroup string
38+
Location string
39+
ExtendedLocation *infrav1.ExtendedLocationSpec
40+
ClusterName string
41+
Role string
42+
NICIDs []string
43+
SSHKeyData string
44+
Size string
45+
AvailabilitySetID string
46+
Zone string
47+
Identity infrav1.VMIdentity
48+
OSDisk infrav1.OSDisk
49+
DataDisks []infrav1.DataDisk
50+
UserAssignedIdentities []infrav1.UserAssignedIdentity
51+
SpotVMOptions *infrav1.SpotVMOptions
52+
SecurityProfile *infrav1.SecurityProfile
53+
AdditionalTags infrav1.Tags
54+
AdditionalCapabilities *infrav1.AdditionalCapabilities
55+
DiagnosticsProfile *infrav1.Diagnostics
56+
CapacityReservationGroupID string
57+
SKU resourceskus.SKU
58+
Image *infrav1.Image
59+
BootstrapData string
60+
ProviderID string
6061
}
6162

6263
// ResourceName returns the name of the virtual machine.
@@ -137,10 +138,11 @@ func (s *VMSpec) Parameters(ctx context.Context, existing interface{}) (params i
137138
NetworkProfile: &armcompute.NetworkProfile{
138139
NetworkInterfaces: s.generateNICRefs(),
139140
},
140-
Priority: priority,
141-
EvictionPolicy: evictionPolicy,
142-
BillingProfile: billingProfile,
143-
DiagnosticsProfile: converters.GetDiagnosticsProfile(s.DiagnosticsProfile),
141+
Priority: priority,
142+
EvictionPolicy: evictionPolicy,
143+
BillingProfile: billingProfile,
144+
DiagnosticsProfile: converters.GetDiagnosticsProfile(s.DiagnosticsProfile),
145+
CapacityReservation: s.getCapacityReservationProfile(),
144146
},
145147
Identity: identity,
146148
Zones: s.getZones(),
@@ -438,3 +440,13 @@ func (s *VMSpec) getZones() []*string {
438440
}
439441
return zones
440442
}
443+
444+
func (s *VMSpec) getCapacityReservationProfile() *armcompute.CapacityReservationProfile {
445+
var crf *armcompute.CapacityReservationProfile
446+
if s.CapacityReservationGroupID != "" {
447+
crf = &armcompute.CapacityReservationProfile{
448+
CapacityReservationGroup: &armcompute.SubResource{ID: &s.CapacityReservationGroupID},
449+
}
450+
}
451+
return crf
452+
}

0 commit comments

Comments
 (0)