Skip to content

Commit c8cf42a

Browse files
committed
fix(machinepool): clear VirtualMachineProfile if no model/custom data update
Scale up/down with MachinePool always updates the VM image model to use. This change sets the VirtualMachineProfile to nil when no change is necessary and ensures therefore less churn on scale up/downs leading to model updates which may require manual fixing Fixes some linting errors related to unnecessary params, too.
1 parent 4fec820 commit c8cf42a

File tree

5 files changed

+254
-40
lines changed

5 files changed

+254
-40
lines changed

azure/scope/machinepool.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type (
8181
capiMachinePoolPatchHelper *patch.Helper
8282
vmssState *azure.VMSS
8383
cache *MachinePoolCache
84+
skuCache *resourceskus.Cache
8485
}
8586

8687
// NodeStatus represents the status of a Kubernetes node.
@@ -164,12 +165,15 @@ func (m *MachinePoolScope) InitMachinePoolCache(ctx context.Context) error {
164165
return err
165166
}
166167

167-
skuCache, err := resourceskus.GetCache(m, m.Location())
168-
if err != nil {
169-
return err
168+
if m.skuCache == nil {
169+
skuCache, err := resourceskus.GetCache(m, m.Location())
170+
if err != nil {
171+
return errors.Wrap(err, "failed to init resourceskus cache")
172+
}
173+
m.skuCache = skuCache
170174
}
171175

172-
m.cache.VMSKU, err = skuCache.Get(ctx, m.AzureMachinePool.Spec.Template.VMSize, resourceskus.VirtualMachines)
176+
m.cache.VMSKU, err = m.skuCache.Get(ctx, m.AzureMachinePool.Spec.Template.VMSize, resourceskus.VirtualMachines)
173177
if err != nil {
174178
return errors.Wrapf(err, "failed to get VM SKU %s in compute api", m.AzureMachinePool.Spec.Template.VMSize)
175179
}
@@ -222,10 +226,8 @@ func (m *MachinePoolScope) ScaleSetSpec(ctx context.Context) azure.ResourceSpecG
222226
}
223227

224228
if m.cache != nil {
225-
if m.HasReplicasExternallyManaged(ctx) {
226-
spec.ShouldPatchCustomData = m.cache.HasBootstrapDataChanges
227-
log.V(4).Info("has bootstrap data changed?", "shouldPatchCustomData", spec.ShouldPatchCustomData)
228-
}
229+
spec.ShouldPatchCustomData = m.cache.HasBootstrapDataChanges
230+
log.V(4).Info("has bootstrap data changed?", "shouldPatchCustomData", spec.ShouldPatchCustomData)
229231
spec.VMSSExtensionSpecs = m.VMSSExtensionSpecs()
230232
spec.SKU = m.cache.VMSKU
231233
spec.VMImage = m.cache.VMImage
@@ -705,11 +707,9 @@ func (m *MachinePoolScope) Close(ctx context.Context) error {
705707
if err := m.updateReplicasAndProviderIDs(ctx); err != nil {
706708
return errors.Wrap(err, "failed to update replicas and providerIDs")
707709
}
708-
if m.HasReplicasExternallyManaged(ctx) {
709-
if err := m.updateCustomDataHash(ctx); err != nil {
710-
// ignore errors to calculating the custom data hash since it's not absolutely crucial.
711-
log.V(4).Error(err, "unable to update custom data hash, ignoring.")
712-
}
710+
if err := m.updateCustomDataHash(ctx); err != nil {
711+
// ignore errors to calculating the custom data hash since it's not absolutely crucial.
712+
log.V(4).Error(err, "unable to update custom data hash, ignoring.")
713713
}
714714
}
715715

azure/scope/machinepool_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ package scope
1818

1919
import (
2020
"context"
21+
"crypto/sha256"
22+
"encoding/base64"
2123
"fmt"
24+
"io"
2225
"reflect"
2326
"testing"
2427
"time"
2528

2629
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
2730
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
31+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
2832
azureautorest "github.com/Azure/go-autorest/autorest/azure"
2933
"github.com/Azure/go-autorest/autorest/azure/auth"
3034
. "github.com/onsi/gomega"
@@ -1576,3 +1580,148 @@ func TestMachinePoolScope_applyAzureMachinePoolMachines(t *testing.T) {
15761580
})
15771581
}
15781582
}
1583+
1584+
func TestBootstrapDataChanges(t *testing.T) {
1585+
ctx, cancel := context.WithCancel(context.Background())
1586+
defer cancel()
1587+
scheme := runtime.NewScheme()
1588+
_ = clusterv1.AddToScheme(scheme)
1589+
_ = infrav1.AddToScheme(scheme)
1590+
_ = infrav1exp.AddToScheme(scheme)
1591+
_ = corev1.AddToScheme(scheme)
1592+
1593+
var (
1594+
g = NewWithT(t)
1595+
mockCtrl = gomock.NewController(t)
1596+
cb = fake.NewClientBuilder().WithScheme(scheme)
1597+
cluster = &clusterv1.Cluster{
1598+
ObjectMeta: metav1.ObjectMeta{
1599+
Name: "cluster1",
1600+
Namespace: "default",
1601+
},
1602+
Spec: clusterv1.ClusterSpec{
1603+
InfrastructureRef: &corev1.ObjectReference{
1604+
Name: "azCluster1",
1605+
},
1606+
},
1607+
Status: clusterv1.ClusterStatus{
1608+
InfrastructureReady: true,
1609+
},
1610+
}
1611+
azureCluster = &infrav1.AzureCluster{
1612+
ObjectMeta: metav1.ObjectMeta{
1613+
Name: "azCluster1",
1614+
Namespace: "default",
1615+
},
1616+
Spec: infrav1.AzureClusterSpec{
1617+
AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
1618+
Location: "test",
1619+
},
1620+
},
1621+
}
1622+
mp = &expv1.MachinePool{
1623+
ObjectMeta: metav1.ObjectMeta{
1624+
Name: "mp1",
1625+
Namespace: "default",
1626+
OwnerReferences: []metav1.OwnerReference{
1627+
{
1628+
Name: "cluster1",
1629+
Kind: "Cluster",
1630+
APIVersion: clusterv1.GroupVersion.String(),
1631+
},
1632+
},
1633+
},
1634+
Spec: expv1.MachinePoolSpec{
1635+
Template: clusterv1.MachineTemplateSpec{
1636+
Spec: clusterv1.MachineSpec{
1637+
Bootstrap: clusterv1.Bootstrap{
1638+
DataSecretName: ptr.To("mp-secret"),
1639+
},
1640+
Version: ptr.To("v1.31.0"),
1641+
},
1642+
},
1643+
},
1644+
}
1645+
bootstrapData = "test"
1646+
bootstrapDataHash = sha256Hash(base64.StdEncoding.EncodeToString([]byte(bootstrapData)))
1647+
bootstrapSecret = corev1.Secret{
1648+
ObjectMeta: metav1.ObjectMeta{
1649+
Name: "mp-secret",
1650+
Namespace: "default",
1651+
},
1652+
Data: map[string][]byte{"value": []byte(bootstrapData)},
1653+
}
1654+
amp = &infrav1exp.AzureMachinePool{
1655+
ObjectMeta: metav1.ObjectMeta{
1656+
Name: "amp1",
1657+
Namespace: "default",
1658+
OwnerReferences: []metav1.OwnerReference{
1659+
{
1660+
Name: "mp1",
1661+
Kind: "MachinePool",
1662+
APIVersion: expv1.GroupVersion.String(),
1663+
},
1664+
},
1665+
Annotations: map[string]string{
1666+
azure.CustomDataHashAnnotation: fmt.Sprintf("%x", bootstrapDataHash),
1667+
},
1668+
},
1669+
Spec: infrav1exp.AzureMachinePoolSpec{
1670+
Template: infrav1exp.AzureMachinePoolMachineTemplate{
1671+
Image: &infrav1.Image{},
1672+
NetworkInterfaces: []infrav1.NetworkInterface{
1673+
{
1674+
SubnetName: "test",
1675+
},
1676+
},
1677+
VMSize: "VM_SIZE",
1678+
},
1679+
},
1680+
}
1681+
vmssState = &azure.VMSS{}
1682+
)
1683+
defer mockCtrl.Finish()
1684+
1685+
s := &MachinePoolScope{
1686+
client: cb.
1687+
WithObjects(&bootstrapSecret).
1688+
Build(),
1689+
ClusterScoper: &ClusterScope{
1690+
Cluster: cluster,
1691+
AzureCluster: azureCluster,
1692+
},
1693+
skuCache: resourceskus.NewStaticCache([]armcompute.ResourceSKU{
1694+
{
1695+
Name: ptr.To("VM_SIZE"),
1696+
},
1697+
}, "test"),
1698+
MachinePool: mp,
1699+
AzureMachinePool: amp,
1700+
vmssState: vmssState,
1701+
}
1702+
1703+
g.Expect(s.InitMachinePoolCache(ctx)).NotTo(HaveOccurred())
1704+
1705+
spec := s.ScaleSetSpec(ctx)
1706+
sSpec := spec.(*scalesets.ScaleSetSpec)
1707+
g.Expect(sSpec.ShouldPatchCustomData).To(Equal(false))
1708+
1709+
amp.Annotations[azure.CustomDataHashAnnotation] = "old"
1710+
1711+
// reset cache to be able to build up the cache again
1712+
s.cache = nil
1713+
g.Expect(s.InitMachinePoolCache(ctx)).NotTo(HaveOccurred())
1714+
1715+
spec = s.ScaleSetSpec(ctx)
1716+
sSpec = spec.(*scalesets.ScaleSetSpec)
1717+
g.Expect(sSpec.ShouldPatchCustomData).To(Equal(true))
1718+
}
1719+
1720+
func sha256Hash(text string) []byte {
1721+
h := sha256.New()
1722+
_, err := io.WriteString(h, text)
1723+
if err != nil {
1724+
panic(err)
1725+
}
1726+
return h.Sum(nil)
1727+
}

azure/services/scalesets/scalesets_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -741,8 +741,8 @@ func newWindowsVMSSSpec() ScaleSetSpec {
741741
return vmss
742742
}
743743

744-
func newDefaultExistingVMSS(vmSize string) armcompute.VirtualMachineScaleSet {
745-
vmss := newDefaultVMSS(vmSize)
744+
func newDefaultExistingVMSS() armcompute.VirtualMachineScaleSet {
745+
vmss := newDefaultVMSS("VM_SIZE")
746746
vmss.ID = ptr.To("subscriptions/1234/resourceGroups/my_resource_group/providers/Microsoft.Compute/virtualMachines/my-vm")
747747
return vmss
748748
}

azure/services/scalesets/spec.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func (s *ScaleSetSpec) OwnerResourceName() string {
9393
}
9494

9595
func (s *ScaleSetSpec) existingParameters(ctx context.Context, existing interface{}) (parameters interface{}, err error) {
96+
ctx, log, done := tele.StartSpanWithLogger(ctx, "scalesets.ScaleSetSpec.existingParameters")
97+
defer done()
98+
9699
existingVMSS, ok := existing.(armcompute.VirtualMachineScaleSet)
97100
if !ok {
98101
return nil, errors.Errorf("%T is not an armcompute.VirtualMachineScaleSet", existing)
@@ -132,6 +135,15 @@ func (s *ScaleSetSpec) existingParameters(ctx context.Context, existing interfac
132135
return nil, nil
133136
}
134137

138+
// if there are no model changes and no change in custom data, remove VirtualMachineProfile to avoid unnecessary VMSS model
139+
// updates.
140+
if !hasModelChanges && !s.ShouldPatchCustomData {
141+
log.V(4).Info("removing virtual machine profile from parameters", "hasModelChanges", hasModelChanges, "shouldPatchCustomData", s.ShouldPatchCustomData)
142+
vmss.Properties.VirtualMachineProfile = nil
143+
} else {
144+
log.V(4).Info("has changes, not removing virtual machine profile from parameters", "hasModelChanges", hasModelChanges, "shouldPatchCustomData", s.ShouldPatchCustomData)
145+
}
146+
135147
return vmss, nil
136148
}
137149

0 commit comments

Comments
 (0)