Skip to content

Commit ebc75ff

Browse files
authored
Merge pull request #1223 from CecileRobertMichon/backport-extension-fix
[backport-release-0.4] Ensure VM and VMSS extensions are applied once
2 parents bf4f39a + e6d25da commit ebc75ff

File tree

11 files changed

+171
-6
lines changed

11 files changed

+171
-6
lines changed

cloud/services/scalesets/mock_scalesets/scalesets_mock.go

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

cloud/services/scalesets/scalesets.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type ScaleSetScope interface {
4242
logr.Logger
4343
azure.ClusterDescriber
4444
ScaleSetSpec() azure.ScaleSetSpec
45+
VMSSExtensionSpecs() []azure.VMSSExtensionSpec
4546
GetBootstrapData(ctx context.Context) (string, error)
4647
GetVMImage() (*infrav1.Image, error)
4748
SetAnnotation(string, string)
@@ -329,6 +330,8 @@ func (s *Service) buildVMSSFromSpec(ctx context.Context, vmssSpec azure.ScaleSet
329330
vmssSpec.AcceleratedNetworking = &accelNet
330331
}
331332

333+
extensions := s.generateExtensions()
334+
332335
storageProfile, err := s.generateStorageProfile(vmssSpec, sku)
333336
if err != nil {
334337
return result, err
@@ -409,6 +412,9 @@ func (s *Service) buildVMSSFromSpec(ctx context.Context, vmssSpec azure.ScaleSet
409412
Priority: priority,
410413
EvictionPolicy: evictionPolicy,
411414
BillingProfile: billingProfile,
415+
ExtensionProfile: &compute.VirtualMachineScaleSetExtensionProfile{
416+
Extensions: &extensions,
417+
},
412418
},
413419
},
414420
}
@@ -504,6 +510,23 @@ func (s *Service) getVirtualMachineScaleSetIfDone(ctx context.Context, future *i
504510
return converters.SDKToVMSS(vmss, vmssInstances), nil
505511
}
506512

513+
func (s *Service) generateExtensions() []compute.VirtualMachineScaleSetExtension {
514+
extensions := make([]compute.VirtualMachineScaleSetExtension, len(s.Scope.VMSSExtensionSpecs()))
515+
for i, extensionSpec := range s.Scope.VMSSExtensionSpecs() {
516+
extensions[i] = compute.VirtualMachineScaleSetExtension{
517+
Name: &extensionSpec.Name,
518+
VirtualMachineScaleSetExtensionProperties: &compute.VirtualMachineScaleSetExtensionProperties{
519+
Publisher: to.StringPtr(extensionSpec.Publisher),
520+
Type: to.StringPtr(extensionSpec.Name),
521+
TypeHandlerVersion: to.StringPtr(extensionSpec.Version),
522+
Settings: nil,
523+
ProtectedSettings: nil,
524+
},
525+
}
526+
}
527+
return extensions
528+
}
529+
507530
// generateStorageProfile generates a pointer to a compute.VirtualMachineScaleSetStorageProfile which can utilized for VM creation.
508531
func (s *Service) generateStorageProfile(vmssSpec azure.ScaleSetSpec, sku resourceskus.SKU) (*compute.VirtualMachineScaleSetStorageProfile, error) {
509532
storageProfile := &compute.VirtualMachineScaleSetStorageProfile{

cloud/services/scalesets/scalesets_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,20 @@ func newDefaultVMSS() compute.VirtualMachineScaleSet {
896896
},
897897
},
898898
},
899+
ExtensionProfile: &compute.VirtualMachineScaleSetExtensionProfile{
900+
Extensions: &[]compute.VirtualMachineScaleSetExtension{
901+
{
902+
Name: to.StringPtr("someExtension"),
903+
VirtualMachineScaleSetExtensionProperties: &compute.VirtualMachineScaleSetExtensionProperties{
904+
Publisher: to.StringPtr("somePublisher"),
905+
Type: to.StringPtr("someExtension"),
906+
TypeHandlerVersion: to.StringPtr("someVersion"),
907+
Settings: nil,
908+
ProtectedSettings: nil,
909+
},
910+
},
911+
},
912+
},
899913
ScheduledEventsProfile: &compute.ScheduledEventsProfile{
900914
TerminateNotificationProfile: &compute.TerminateNotificationProfile{
901915
Enable: to.BoolPtr(true),
@@ -984,6 +998,13 @@ func setupDefaultVMSSExpectations(s *mock_scalesets.MockScaleSetScopeMockRecorde
984998
Version: "1.0",
985999
},
9861000
}, nil)
1001+
s.VMSSExtensionSpecs().Return([]azure.VMSSExtensionSpec{
1002+
{
1003+
Name: "someExtension",
1004+
Publisher: "somePublisher",
1005+
Version: "someVersion",
1006+
},
1007+
}).AnyTimes()
9871008
}
9881009

9891010
func setupDefaultVMSSUpdateExpectations(s *mock_scalesets.MockScaleSetScopeMockRecorder) {

cloud/services/vmextensions/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
// Client wraps go-sdk
2929
type client interface {
30+
Get(ctx context.Context, resourceGroupName, vmName, name string) (compute.VirtualMachineExtension, error)
3031
CreateOrUpdate(context.Context, string, string, string, compute.VirtualMachineExtension) error
3132
Delete(context.Context, string, string, string) error
3233
}
@@ -51,6 +52,14 @@ func newVirtualMachineExtensionsClient(subscriptionID string, baseURI string, au
5152
return vmextensionsClient
5253
}
5354

55+
// Get the virtual machine extension
56+
func (ac *azureClient) Get(ctx context.Context, resourceGroupName, vmName, name string) (compute.VirtualMachineExtension, error) {
57+
ctx, span := tele.Tracer().Start(ctx, "vmextensions.AzureClient.Get")
58+
defer span.End()
59+
60+
return ac.vmextensions.Get(ctx, resourceGroupName, vmName, name, "")
61+
}
62+
5463
// CreateOrUpdate creates or updates the virtual machine extension
5564
func (ac *azureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, vmName, name string, parameters compute.VirtualMachineExtension) error {
5665
ctx, span := tele.Tracer().Start(ctx, "vmextensions.AzureClient.CreateOrUpdate")

cloud/services/vmextensions/mock_vmextensions/client_mock.go

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

cloud/services/vmextensions/vmextensions.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ func (s *Service) Reconcile(ctx context.Context) error {
5555
defer span.End()
5656

5757
for _, extensionSpec := range s.Scope.VMExtensionSpecs() {
58+
if _, err := s.client.Get(ctx, s.Scope.ResourceGroup(), extensionSpec.VMName, extensionSpec.Name); err == nil {
59+
// check for the extension and don't update if already exists
60+
// TODO: set conditions based on extension status
61+
continue
62+
}
5863
s.Scope.V(2).Info("creating VM extension", "vm extension", extensionSpec.Name)
5964
err := s.client.CreateOrUpdate(
6065
ctx,
@@ -81,6 +86,6 @@ func (s *Service) Reconcile(ctx context.Context) error {
8186
}
8287

8388
// Delete is a no-op. Extensions will be deleted as part of VM deletion.
84-
func (s *Service) Delete(ctx context.Context) error {
89+
func (s *Service) Delete(_ context.Context) error {
8590
return nil
8691
}

cloud/services/vmextensions/vmextensions_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ func TestReconcileVMExtension(t *testing.T) {
3838
expectedError string
3939
expect func(s *mock_vmextensions.MockVMExtensionScopeMockRecorder, m *mock_vmextensions.MockclientMockRecorder)
4040
}{
41+
{
42+
name: "extension already exists",
43+
expectedError: "",
44+
expect: func(s *mock_vmextensions.MockVMExtensionScopeMockRecorder, m *mock_vmextensions.MockclientMockRecorder) {
45+
s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
46+
s.VMExtensionSpecs().Return([]azure.VMExtensionSpec{
47+
{
48+
Name: "my-extension-1",
49+
VMName: "my-vm",
50+
Publisher: "some-publisher",
51+
Version: "1.0",
52+
},
53+
})
54+
s.ResourceGroup().AnyTimes().Return("my-rg")
55+
s.Location().AnyTimes().Return("test-location")
56+
m.Get(gomockinternal.AContext(), "my-rg", "my-vm", "my-extension-1")
57+
},
58+
},
4159
{
4260
name: "reconcile multiple extensions",
4361
expectedError: "",
@@ -59,7 +77,11 @@ func TestReconcileVMExtension(t *testing.T) {
5977
})
6078
s.ResourceGroup().AnyTimes().Return("my-rg")
6179
s.Location().AnyTimes().Return("test-location")
80+
m.Get(gomockinternal.AContext(), "my-rg", "my-vm", "my-extension-1").
81+
Return(compute.VirtualMachineExtension{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
6282
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-vm", "my-extension-1", gomock.AssignableToTypeOf(compute.VirtualMachineExtension{}))
83+
m.Get(gomockinternal.AContext(), "my-rg", "my-vm", "other-extension").
84+
Return(compute.VirtualMachineExtension{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
6385
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-vm", "other-extension", gomock.AssignableToTypeOf(compute.VirtualMachineExtension{}))
6486
},
6587
},
@@ -84,6 +106,8 @@ func TestReconcileVMExtension(t *testing.T) {
84106
})
85107
s.ResourceGroup().AnyTimes().Return("my-rg")
86108
s.Location().AnyTimes().Return("test-location")
109+
m.Get(gomockinternal.AContext(), "my-rg", "my-vm", "my-extension-1").
110+
Return(compute.VirtualMachineExtension{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
87111
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-vm", "my-extension-1", gomock.AssignableToTypeOf(compute.VirtualMachineExtension{})).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error"))
88112

89113
},

cloud/services/vmssextensions/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
// Client wraps go-sdk
2929
type client interface {
30+
Get(context.Context, string, string, string) (compute.VirtualMachineScaleSetExtension, error)
3031
CreateOrUpdate(context.Context, string, string, string, compute.VirtualMachineScaleSetExtension) error
3132
Delete(context.Context, string, string, string) error
3233
}
@@ -51,6 +52,14 @@ func newVirtualMachineScaleSetExtensionsClient(subscriptionID string, baseURI st
5152
return vmssextensionsClient
5253
}
5354

55+
// Get creates or updates the virtual machine scale set extension
56+
func (ac *azureClient) Get(ctx context.Context, resourceGroupName, vmssName, name string) (compute.VirtualMachineScaleSetExtension, error) {
57+
ctx, span := tele.Tracer().Start(ctx, "vmssextensions.AzureClient.Get")
58+
defer span.End()
59+
60+
return ac.vmssextensions.Get(ctx, resourceGroupName, vmssName, name, "")
61+
}
62+
5463
// CreateOrUpdate creates or updates the virtual machine scale set extension
5564
func (ac *azureClient) CreateOrUpdate(ctx context.Context, resourceGroupName, vmName, name string, parameters compute.VirtualMachineScaleSetExtension) error {
5665
ctx, span := tele.Tracer().Start(ctx, "vmssextensions.AzureClient.CreateOrUpdate")

cloud/services/vmssextensions/mock_vmssextensions/client_mock.go

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

cloud/services/vmssextensions/vmssextensions.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ func (s *Service) Reconcile(ctx context.Context) error {
5555
defer span.End()
5656

5757
for _, extensionSpec := range s.Scope.VMSSExtensionSpecs() {
58-
s.Scope.V(2).Info("creating VM extension", "vm extension", extensionSpec.Name)
58+
if _, err := s.client.Get(ctx, s.Scope.ResourceGroup(), extensionSpec.ScaleSetName, extensionSpec.Name); err == nil {
59+
// check for the extension and don't update if already exists
60+
// TODO: set conditions based on extension status
61+
continue
62+
}
63+
64+
s.Scope.V(2).Info("creating VMSS extension", "vssm extension", extensionSpec.Name)
5965
err := s.client.CreateOrUpdate(
6066
ctx,
6167
s.Scope.ResourceGroup(),
@@ -72,14 +78,14 @@ func (s *Service) Reconcile(ctx context.Context) error {
7278
},
7379
)
7480
if err != nil {
75-
return errors.Wrapf(err, "failed to create VM extension %s on scale set %s in resource group %s", extensionSpec.Name, extensionSpec.ScaleSetName, s.Scope.ResourceGroup())
81+
return errors.Wrapf(err, "failed to create VMSS extension %s on scale set %s in resource group %s", extensionSpec.Name, extensionSpec.ScaleSetName, s.Scope.ResourceGroup())
7682
}
77-
s.Scope.V(2).Info("successfully created VM extension", "vm extension", extensionSpec.Name)
83+
s.Scope.V(2).Info("successfully created VMSS extension", "vm extension", extensionSpec.Name)
7884
}
7985
return nil
8086
}
8187

8288
// Delete is a no-op. Extensions will be deleted as part of VMSS deletion.
83-
func (s *Service) Delete(ctx context.Context) error {
89+
func (s *Service) Delete(_ context.Context) error {
8490
return nil
8591
}

0 commit comments

Comments
 (0)