Skip to content

Commit 4f65eac

Browse files
committed
fix(MachinePool): always update vmssState
Updates ScaleSets service to always update the scope's vmss state with the latest data. Previously, a long running operation would cause the vmssState to not be updated and delayed creation of AzureMachinePoolMachines until much later when that long running operation completed. This change avoids this and updates the vmssState to what is retrieved from the API at all times. The call does not make more API requests because the VMSS GET request was done anyway but its result ignored.
1 parent 5b2cbce commit 4f65eac

File tree

2 files changed

+42
-37
lines changed

2 files changed

+42
-37
lines changed

azure/services/scalesets/scalesets.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (s *Service) Reconcile(ctx context.Context) (retErr error) {
9797
return errors.Errorf("%T is not of type ScaleSetSpec", spec)
9898
}
9999

100-
_, err := s.Client.Get(ctx, spec)
100+
result, err := s.Client.Get(ctx, spec)
101101
if err == nil {
102102
// We can only get the existing instances if the VMSS already exists
103103
scaleSetSpec.VMSSInstances, err = s.Client.ListInstances(ctx, spec.ResourceGroupName(), spec.ResourceName())
@@ -106,34 +106,51 @@ func (s *Service) Reconcile(ctx context.Context) (retErr error) {
106106
s.Scope.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, err)
107107
return err
108108
}
109+
if result != nil {
110+
if err := s.updateScopeState(ctx, result, scaleSetSpec); err != nil {
111+
return err
112+
}
113+
}
109114
} else if !azure.ResourceNotFound(err) {
110115
return errors.Wrapf(err, "failed to get existing VMSS")
111116
}
112117

113-
result, err := s.CreateOrUpdateResource(ctx, scaleSetSpec, serviceName)
118+
result, err = s.CreateOrUpdateResource(ctx, scaleSetSpec, serviceName)
114119
s.Scope.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, err)
115120

116121
if err == nil && result != nil {
117-
vmss, ok := result.(armcompute.VirtualMachineScaleSet)
118-
if !ok {
119-
return errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", result)
122+
if err := s.updateScopeState(ctx, result, scaleSetSpec); err != nil {
123+
return err
120124
}
125+
}
121126

122-
fetchedVMSS := converters.SDKToVMSS(vmss, scaleSetSpec.VMSSInstances)
123-
if err := s.Scope.ReconcileReplicas(ctx, &fetchedVMSS); err != nil {
124-
return errors.Wrap(err, "unable to reconcile VMSS replicas")
125-
}
127+
return err
128+
}
126129

127-
// Transform the VMSS resource representation to conform to the cloud-provider-azure representation
128-
providerID, err := azprovider.ConvertResourceGroupNameToLower(azureutil.ProviderIDPrefix + fetchedVMSS.ID)
129-
if err != nil {
130-
return errors.Wrapf(err, "failed to parse VMSS ID %s", fetchedVMSS.ID)
131-
}
132-
s.Scope.SetProviderID(providerID)
133-
s.Scope.SetVMSSState(&fetchedVMSS)
130+
// updateScopeState updates the scope's VMSS state and provider ID
131+
//
132+
// Code later in the reconciler uses scope's VMSS state for determining scale status and whether to create/delete
133+
// AzureMachinePoolMachines.
134+
// N.B.: before calling this function, make sure scaleSetSpec.VMSSInstances is updated to the latest state.
135+
func (s *Service) updateScopeState(ctx context.Context, result interface{}, scaleSetSpec *ScaleSetSpec) error {
136+
vmss, ok := result.(armcompute.VirtualMachineScaleSet)
137+
if !ok {
138+
return errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", result)
134139
}
135140

136-
return err
141+
fetchedVMSS := converters.SDKToVMSS(vmss, scaleSetSpec.VMSSInstances)
142+
if err := s.Scope.ReconcileReplicas(ctx, &fetchedVMSS); err != nil {
143+
return errors.Wrap(err, "unable to reconcile VMSS replicas")
144+
}
145+
146+
// Transform the VMSS resource representation to conform to the cloud-provider-azure representation
147+
providerID, err := azprovider.ConvertResourceGroupNameToLower(azureutil.ProviderIDPrefix + fetchedVMSS.ID)
148+
if err != nil {
149+
return errors.Wrapf(err, "failed to parse VMSS ID %s", fetchedVMSS.ID)
150+
}
151+
s.Scope.SetProviderID(providerID)
152+
s.Scope.SetVMSSState(&fetchedVMSS)
153+
return nil
137154
}
138155

139156
// Delete deletes a scale set asynchronously. Delete sends a DELETE request to Azure and if accepted without error,

azure/services/scalesets/scalesets_test.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,14 @@ func TestReconcileVMSS(t *testing.T) {
120120
spec := getDefaultVMSSSpec()
121121
// Validate spec
122122
s.ScaleSetSpec(gomockinternal.AContext()).Return(spec).AnyTimes()
123-
m.Get(gomockinternal.AContext(), &defaultSpec).Return(&resultVMSS, nil)
123+
m.Get(gomockinternal.AContext(), &defaultSpec).Return(resultVMSS, nil)
124124
m.ListInstances(gomockinternal.AContext(), defaultSpec.ResourceGroup, defaultSpec.Name).Return(defaultInstances, nil)
125125
r.CreateOrUpdateResource(gomockinternal.AContext(), spec, serviceName).Return(getResultVMSS(), nil)
126126
s.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, nil)
127127

128-
s.ReconcileReplicas(gomockinternal.AContext(), &fetchedVMSS).Return(nil)
129-
s.SetProviderID(azureutil.ProviderIDPrefix + defaultVMSSID)
130-
s.SetVMSSState(&fetchedVMSS)
128+
s.ReconcileReplicas(gomockinternal.AContext(), &fetchedVMSS).Return(nil).Times(2)
129+
s.SetProviderID(azureutil.ProviderIDPrefix + defaultVMSSID).Times(2)
130+
s.SetVMSSState(&fetchedVMSS).Times(2)
131131
},
132132
},
133133
{
@@ -178,28 +178,16 @@ func TestReconcileVMSS(t *testing.T) {
178178
s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
179179
spec := getDefaultVMSSSpec()
180180
s.ScaleSetSpec(gomockinternal.AContext()).Return(spec).AnyTimes()
181-
m.Get(gomockinternal.AContext(), &defaultSpec).Return(&resultVMSS, nil)
181+
m.Get(gomockinternal.AContext(), &defaultSpec).Return(resultVMSS, nil)
182182
m.ListInstances(gomockinternal.AContext(), defaultSpec.ResourceGroup, defaultSpec.Name).Return(defaultInstances, nil)
183183

184184
r.CreateOrUpdateResource(gomockinternal.AContext(), spec, serviceName).
185185
Return(nil, internalError())
186186
s.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, internalError())
187-
},
188-
},
189-
{
190-
name: "failed to reconcile replicas",
191-
expectedError: "unable to reconcile VMSS replicas:.*#: Internal Server Error: StatusCode=500",
192-
expect: func(g *WithT, s *mock_scalesets.MockScaleSetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder, m *mock_scalesets.MockClientMockRecorder) {
193-
s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
194-
spec := getDefaultVMSSSpec()
195-
s.ScaleSetSpec(gomockinternal.AContext()).Return(spec).AnyTimes()
196-
m.Get(gomockinternal.AContext(), &defaultSpec).Return(&resultVMSS, nil)
197-
m.ListInstances(gomockinternal.AContext(), defaultSpec.ResourceGroup, defaultSpec.Name).Return(defaultInstances, nil)
198187

199-
r.CreateOrUpdateResource(gomockinternal.AContext(), spec, serviceName).Return(getResultVMSS(), nil)
200-
s.UpdatePutStatus(infrav1.BootstrapSucceededCondition, serviceName, nil)
201-
202-
s.ReconcileReplicas(gomockinternal.AContext(), &fetchedVMSS).Return(internalError())
188+
s.ReconcileReplicas(gomockinternal.AContext(), &fetchedVMSS).Return(nil)
189+
s.SetProviderID(azureutil.ProviderIDPrefix + defaultVMSSID)
190+
s.SetVMSSState(&fetchedVMSS)
203191
},
204192
},
205193
{

0 commit comments

Comments
 (0)