Skip to content

Commit

Permalink
VMSS Flex support for MachinePools
Browse files Browse the repository at this point in the history
  • Loading branch information
mboersma committed Jan 9, 2023
1 parent eef883f commit 60f0ac0
Show file tree
Hide file tree
Showing 40 changed files with 1,691 additions and 79 deletions.
11 changes: 11 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,3 +873,14 @@ type UserManagedBootDiagnostics struct {
// +kubebuilder:validation:MaxLength=1024
StorageAccountURI string `json:"storageAccountURI"`
}

// OrchestrationModeType represents the orchestration mode for a Virtual Machine Scale Set backing an AzureMachinePool.
// +kubebuilder:validation:Enum=Flexible;Uniform
type OrchestrationModeType string

const (
// FlexibleOrchestrationMode treats VMs as individual resources accessible by standard VM APIs.
FlexibleOrchestrationMode OrchestrationModeType = "Flexible"
// UniformOrchestrationMode treats VMs as identical instances accessible by the VMSS VM API.
UniformOrchestrationMode OrchestrationModeType = "Uniform"
)
40 changes: 40 additions & 0 deletions azure/converters/vmss.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,38 @@ func SDKToVMSS(sdkvmss compute.VirtualMachineScaleSet, sdkinstances []compute.Vi
return vmss
}

// SDKVMToVMSSVM converts an Azure SDK VM to a VMSS VM.
func SDKVMToVMSSVM(sdkInstance compute.VirtualMachine) *azure.VMSSVM {
instance := azure.VMSSVM{
ID: to.String(sdkInstance.ID),
}

if sdkInstance.VirtualMachineProperties == nil {
return &instance
}

instance.State = infrav1.Creating
if sdkInstance.ProvisioningState != nil {
instance.State = infrav1.ProvisioningState(to.String(sdkInstance.ProvisioningState))
}

if sdkInstance.OsProfile != nil && sdkInstance.OsProfile.ComputerName != nil {
instance.Name = *sdkInstance.OsProfile.ComputerName
}

if sdkInstance.StorageProfile != nil && sdkInstance.StorageProfile.ImageReference != nil {
imageRef := sdkInstance.StorageProfile.ImageReference
instance.Image = SDKImageToImage(imageRef, sdkInstance.Plan != nil)
}

if sdkInstance.Zones != nil && len(*sdkInstance.Zones) > 0 {
// An instance should have only 1 zone, so use the first item of the slice.
instance.AvailabilityZone = to.StringSlice(sdkInstance.Zones)[0]
}

return &instance
}

// SDKToVMSSVM converts an Azure SDK VirtualMachineScaleSetVM into an infrav1exp.VMSSVM.
func SDKToVMSSVM(sdkInstance compute.VirtualMachineScaleSetVM) *azure.VMSSVM {
// Convert resourceGroup Name ID ( ProviderID in capz objects )
Expand Down Expand Up @@ -117,3 +149,11 @@ func SDKImageToImage(sdkImageRef *compute.ImageReference, isThirdPartyImage bool
},
}
}

// GetOrchestrationMode returns the compute.OrchestrationMode for the given infrav1.OrchestrationModeType.
func GetOrchestrationMode(modeType infrav1.OrchestrationModeType) compute.OrchestrationMode {
if modeType == infrav1.FlexibleOrchestrationMode {
return compute.OrchestrationModeFlexible
}
return compute.OrchestrationModeUniform
}
99 changes: 99 additions & 0 deletions azure/converters/vmss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,102 @@ func Test_SDKImageToImage(t *testing.T) {
})
}
}

func Test_SDKVMToVMSSVM(t *testing.T) {
cases := []struct {
Name string
Subject compute.VirtualMachine
Expected *azure.VMSSVM
}{
{
Name: "minimal VM",
Subject: compute.VirtualMachine{
ID: to.StringPtr("vmID1"),
},
Expected: &azure.VMSSVM{
ID: "vmID1",
},
},
{
Name: "VM with zones",
Subject: compute.VirtualMachine{
ID: to.StringPtr("vmID2"),
VirtualMachineProperties: &compute.VirtualMachineProperties{
OsProfile: &compute.OSProfile{
ComputerName: to.StringPtr("vmwithzones"),
},
},
Zones: to.StringSlicePtr([]string{"zone0", "zone1"}),
},
Expected: &azure.VMSSVM{
ID: "vmID2",
Name: "vmwithzones",
State: "Creating",
AvailabilityZone: "zone0",
},
},
{
Name: "VM with storage",
Subject: compute.VirtualMachine{
ID: to.StringPtr("vmID3"),
VirtualMachineProperties: &compute.VirtualMachineProperties{
OsProfile: &compute.OSProfile{
ComputerName: to.StringPtr("vmwithstorage"),
},
StorageProfile: &compute.StorageProfile{
ImageReference: &compute.ImageReference{
ID: to.StringPtr("imageID"),
},
},
},
},
Expected: &azure.VMSSVM{
ID: "vmID3",
Image: infrav1.Image{
ID: to.StringPtr("imageID"),
Marketplace: &infrav1.AzureMarketplaceImage{},
},
Name: "vmwithstorage",
State: "Creating",
},
},
{
Name: "VM with provisioning state",
Subject: compute.VirtualMachine{
ID: to.StringPtr("vmID4"),
VirtualMachineProperties: &compute.VirtualMachineProperties{
OsProfile: &compute.OSProfile{
ComputerName: to.StringPtr("vmwithstate"),
},
ProvisioningState: to.StringPtr("Succeeded"),
},
},
Expected: &azure.VMSSVM{
ID: "vmID4",
Name: "vmwithstate",
State: "Succeeded",
},
},
}

for _, c := range cases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
g := gomega.NewGomegaWithT(t)
subject := converters.SDKVMToVMSSVM(c.Subject)
g.Expect(subject).To(gomega.Equal(c.Expected))
})
}
}

func Test_GetOrchestrationMode(t *testing.T) {
g := gomega.NewGomegaWithT(t)

g.Expect(converters.GetOrchestrationMode(infrav1.FlexibleOrchestrationMode)).
To(gomega.Equal(compute.OrchestrationModeFlexible))
g.Expect(converters.GetOrchestrationMode(infrav1.UniformOrchestrationMode)).
To(gomega.Equal(compute.OrchestrationModeUniform))
g.Expect(converters.GetOrchestrationMode("invalid")).
To(gomega.Equal(compute.OrchestrationModeUniform))
}
17 changes: 11 additions & 6 deletions azure/scope/machinepool.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ package scope
import (
"context"
"encoding/base64"
"fmt"
"strings"
"time"

azureautorest "github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -136,6 +139,7 @@ func (m *MachinePoolScope) ScaleSetSpec() azure.ScaleSetSpec {
FailureDomains: m.MachinePool.Spec.FailureDomains,
TerminateNotificationTimeout: m.AzureMachinePool.Spec.Template.TerminateNotificationTimeout,
NetworkInterfaces: m.AzureMachinePool.Spec.Template.NetworkInterfaces,
OrchestrationMode: m.AzureMachinePool.Spec.OrchestrationMode,
}
}

Expand Down Expand Up @@ -339,17 +343,18 @@ func (m *MachinePoolScope) applyAzureMachinePoolMachines(ctx context.Context) er
}

func (m *MachinePoolScope) createMachine(ctx context.Context, machine azure.VMSSVM) error {
if machine.InstanceID == "" {
return errors.New("machine.InstanceID must not be empty")
}
ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.MachinePoolScope.createMachine")
defer done()

if machine.Name == "" {
return errors.New("machine.Name must not be empty")
parsed, err := azureautorest.ParseResourceID(machine.ID)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to parse resource id %q", machine.ID))
}
instanceID := strings.ReplaceAll(parsed.ResourceName, "_", "-")

ampm := infrav1exp.AzureMachinePoolMachine{
ObjectMeta: metav1.ObjectMeta{
Name: m.AzureMachinePool.Name + "-" + machine.InstanceID,
Name: m.AzureMachinePool.Name + "-" + instanceID,
Namespace: m.AzureMachinePool.Namespace,
OwnerReferences: []metav1.OwnerReference{
{
Expand Down
35 changes: 29 additions & 6 deletions azure/services/scalesets/scalesets.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,21 @@ func (s *Service) patchVMSSIfNeeded(ctx context.Context, infraVMSS *azure.VMSS)
}

hasModelChanges := hasModelModifyingDifferences(infraVMSS, vmss)
if maxSurge > 0 && (hasModelChanges || !infraVMSS.HasEnoughLatestModelOrNotMixedModel()) {
var isFlex bool
for _, instance := range infraVMSS.Instances {
if instance.IsFlex() {
isFlex = true
break
}
}
updated := true
if !isFlex {
updated = infraVMSS.HasEnoughLatestModelOrNotMixedModel()
}
if maxSurge > 0 && (hasModelChanges || !updated) {
// surge capacity with the intention of lowering during instance reconciliation
surge := spec.Capacity + int64(maxSurge)
log.V(4).Info("surging...", "surge", surge)
log.V(4).Info("surging...", "surge", surge, "hasModelChanges", hasModelChanges, "updated", updated)
patch.Sku.Capacity = to.Int64Ptr(surge)
}

Expand Down Expand Up @@ -468,6 +479,7 @@ func (s *Service) buildVMSSFromSpec(ctx context.Context, vmssSpec azure.ScaleSet
return compute.VirtualMachineScaleSet{}, err
}

orchestrationMode := converters.GetOrchestrationMode(s.Scope.ScaleSetSpec().OrchestrationMode)
vmss := compute.VirtualMachineScaleSet{
Location: to.StringPtr(s.Scope.Location()),
Sku: &compute.Sku{
Expand All @@ -478,11 +490,8 @@ func (s *Service) buildVMSSFromSpec(ctx context.Context, vmssSpec azure.ScaleSet
Zones: to.StringSlicePtr(vmssSpec.FailureDomains),
Plan: s.generateImagePlan(ctx),
VirtualMachineScaleSetProperties: &compute.VirtualMachineScaleSetProperties{
OrchestrationMode: orchestrationMode,
SinglePlacementGroup: to.BoolPtr(false),
UpgradePolicy: &compute.UpgradePolicy{
Mode: compute.UpgradeModeManual,
},
Overprovision: to.BoolPtr(false),
VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{
OsProfile: osProfile,
StorageProfile: storageProfile,
Expand Down Expand Up @@ -523,6 +532,20 @@ func (s *Service) buildVMSSFromSpec(ctx context.Context, vmssSpec azure.ScaleSet
},
}

// Set properties specific to VMSS orchestration mode
switch orchestrationMode {
case compute.OrchestrationModeUniform:
vmss.VirtualMachineScaleSetProperties.Overprovision = to.BoolPtr(false)
vmss.VirtualMachineScaleSetProperties.UpgradePolicy = &compute.UpgradePolicy{Mode: compute.UpgradeModeManual}
case compute.OrchestrationModeFlexible:
vmss.VirtualMachineScaleSetProperties.VirtualMachineProfile.NetworkProfile.NetworkAPIVersion =
compute.NetworkAPIVersionTwoZeroTwoZeroHyphenMinusOneOneHyphenMinusZeroOne
vmss.VirtualMachineScaleSetProperties.PlatformFaultDomainCount = to.Int32Ptr(1)
if len(vmssSpec.FailureDomains) > 1 {
vmss.VirtualMachineScaleSetProperties.PlatformFaultDomainCount = to.Int32Ptr(int32(len(vmssSpec.FailureDomains)))
}
}

// Use custom NIC definitions in VMSS if set
if len(vmssSpec.NetworkInterfaces) > 0 {
nicConfigs := []compute.VirtualMachineScaleSetNetworkConfiguration{}
Expand Down
3 changes: 2 additions & 1 deletion azure/services/scalesets/scalesets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,8 @@ func newDefaultVMSS(vmSize string) compute.VirtualMachineScaleSet {
UpgradePolicy: &compute.UpgradePolicy{
Mode: compute.UpgradeModeManual,
},
Overprovision: to.BoolPtr(false),
Overprovision: to.BoolPtr(false),
OrchestrationMode: compute.OrchestrationModeUniform,
VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{
OsProfile: &compute.VirtualMachineScaleSetOSProfile{
ComputerNamePrefix: to.StringPtr(defaultVMSSName),
Expand Down
14 changes: 14 additions & 0 deletions azure/services/scalesetvms/mock_scalesetvms/scalesetvms_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 60f0ac0

Please sign in to comment.