Skip to content

Commit 68dcfbf

Browse files
committed
Add Windows support to vmss
1 parent 2bc6a35 commit 68dcfbf

12 files changed

+572
-26
lines changed

cloud/services/scalesets/scalesets.go

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/Azure/go-autorest/autorest/to"
2626
"github.com/go-logr/logr"
2727
"github.com/pkg/errors"
28+
"sigs.k8s.io/cluster-api-provider-azure/util/generators"
2829

2930
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3"
3031
azure "sigs.k8s.io/cluster-api-provider-azure/cloud"
@@ -142,13 +143,9 @@ func (s *Service) Reconcile(ctx context.Context) error {
142143
}
143144
}
144145

145-
sshKey, err := base64.StdEncoding.DecodeString(vmssSpec.SSHKeyData)
146-
if err != nil {
147-
return errors.Wrapf(err, "failed to decode ssh public key")
148-
}
149-
bootstrapData, err := s.Scope.GetBootstrapData(ctx)
146+
osProfile, err := s.generateOSProfile(ctx, vmssSpec)
150147
if err != nil {
151-
return errors.Wrap(err, "failed to retrieve bootstrap data")
148+
return err
152149
}
153150

154151
vmss := compute.VirtualMachineScaleSet{
@@ -170,22 +167,7 @@ func (s *Service) Reconcile(ctx context.Context) error {
170167
Mode: compute.UpgradeModeManual,
171168
},
172169
VirtualMachineProfile: &compute.VirtualMachineScaleSetVMProfile{
173-
OsProfile: &compute.VirtualMachineScaleSetOSProfile{
174-
ComputerNamePrefix: to.StringPtr(vmssSpec.Name),
175-
AdminUsername: to.StringPtr(azure.DefaultUserName),
176-
CustomData: to.StringPtr(bootstrapData),
177-
LinuxConfiguration: &compute.LinuxConfiguration{
178-
SSH: &compute.SSHConfiguration{
179-
PublicKeys: &[]compute.SSHPublicKey{
180-
{
181-
Path: to.StringPtr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", azure.DefaultUserName)),
182-
KeyData: to.StringPtr(string(sshKey)),
183-
},
184-
},
185-
},
186-
DisablePasswordAuthentication: to.BoolPtr(true),
187-
},
188-
},
170+
OsProfile: osProfile,
189171
StorageProfile: storageProfile,
190172
SecurityProfile: securityProfile,
191173
DiagnosticsProfile: &compute.DiagnosticsProfile{
@@ -399,6 +381,52 @@ func (s *Service) generateStorageProfile(vmssSpec azure.ScaleSetSpec, sku resour
399381
return storageProfile, nil
400382
}
401383

384+
func (s *Service) generateOSProfile(ctx context.Context, vmssSpec azure.ScaleSetSpec) (*compute.VirtualMachineScaleSetOSProfile, error) {
385+
sshKey, err := base64.StdEncoding.DecodeString(vmssSpec.SSHKeyData)
386+
if err != nil {
387+
return nil, errors.Wrapf(err, "failed to decode ssh public key")
388+
}
389+
bootstrapData, err := s.Scope.GetBootstrapData(ctx)
390+
if err != nil {
391+
return nil, errors.Wrap(err, "failed to retrieve bootstrap data")
392+
}
393+
394+
osProfile := &compute.VirtualMachineScaleSetOSProfile{
395+
ComputerNamePrefix: to.StringPtr(vmssSpec.Name),
396+
AdminUsername: to.StringPtr(azure.DefaultUserName),
397+
CustomData: to.StringPtr(bootstrapData),
398+
}
399+
400+
switch vmssSpec.OSDisk.OSType {
401+
case string(compute.Windows):
402+
// Cloudbase-init is used to generate a password.
403+
// https://cloudbase-init.readthedocs.io/en/latest/plugins.html#setting-password-main
404+
//
405+
// We generate a random password here in case of failure
406+
// but the password on the VM will NOT be the same as created here.
407+
// Access is provided via SSH public key that is set during deployment
408+
// Azure also provides a way to reset user passwords in the case of need.
409+
osProfile.AdminPassword = to.StringPtr(generators.SudoRandomPassword(123))
410+
osProfile.WindowsConfiguration = &compute.WindowsConfiguration{
411+
EnableAutomaticUpdates: to.BoolPtr(false),
412+
}
413+
default:
414+
osProfile.LinuxConfiguration = &compute.LinuxConfiguration{
415+
DisablePasswordAuthentication: to.BoolPtr(true),
416+
SSH: &compute.SSHConfiguration{
417+
PublicKeys: &[]compute.SSHPublicKey{
418+
{
419+
Path: to.StringPtr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", azure.DefaultUserName)),
420+
KeyData: to.StringPtr(string(sshKey)),
421+
},
422+
},
423+
},
424+
}
425+
}
426+
427+
return osProfile, nil
428+
}
429+
402430
func getVMSSUpdateFromVMSS(vmss compute.VirtualMachineScaleSet) (compute.VirtualMachineScaleSetUpdate, error) {
403431
json, err := vmss.MarshalJSON()
404432
if err != nil {

cloud/services/scalesets/scalesets_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,80 @@ func TestReconcileVMSS(t *testing.T) {
383383
}))
384384
},
385385
},
386+
{
387+
name: "can create a vmss with a Windows node",
388+
expectedError: "",
389+
expect: func(g *gomega.WithT, s *mock_scalesets.MockScaleSetScopeMockRecorder, m *mock_scalesets.MockClientMockRecorder) {
390+
s.ScaleSetSpec().Return(azure.ScaleSetSpec{
391+
Name: "my-vmss",
392+
Size: "VM_SIZE_EAH",
393+
Capacity: 2,
394+
SSHKeyData: "ZmFrZXNzaGtleQo=",
395+
OSDisk: infrav1.OSDisk{
396+
OSType: "Windows",
397+
DiskSizeGB: 120,
398+
ManagedDisk: infrav1.ManagedDisk{
399+
StorageAccountType: "Premium_LRS",
400+
},
401+
},
402+
SubnetName: "my-subnet",
403+
VNetName: "my-vnet",
404+
VNetResourceGroup: "my-rg",
405+
PublicLBName: "capz-lb",
406+
PublicLBAddressPoolName: "backendPool",
407+
AcceleratedNetworking: nil,
408+
TerminateNotificationTimeout: to.IntPtr(7),
409+
})
410+
s.SubscriptionID().AnyTimes().Return("123")
411+
s.ResourceGroup().AnyTimes().Return("my-rg")
412+
s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New())
413+
s.AdditionalTags()
414+
s.Location().Return("test-location")
415+
s.ClusterName().Return("my-cluster")
416+
m.Get(gomockinternal.AContext(), "my-rg", "my-vmss").
417+
Return(compute.VirtualMachineScaleSet{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found"))
418+
s.GetVMImage().Return(&infrav1.Image{
419+
Marketplace: &infrav1.AzureMarketplaceImage{
420+
Publisher: "fake-publisher",
421+
Offer: "my-offer",
422+
SKU: "sku-id",
423+
Version: "1.0",
424+
},
425+
}, nil)
426+
s.GetBootstrapData(gomockinternal.AContext()).Return("fake-bootstrap-data", nil)
427+
m.CreateOrUpdate(gomockinternal.AContext(), "my-rg", "my-vmss", gomock.AssignableToTypeOf(compute.VirtualMachineScaleSet{})).Do(
428+
func(_, _, _ interface{}, vmss compute.VirtualMachineScaleSet) {
429+
g.Expect(vmss.VirtualMachineScaleSetProperties.VirtualMachineProfile.StorageProfile.OsDisk.OsType).To(Equal(compute.Windows))
430+
g.Expect(*vmss.VirtualMachineScaleSetProperties.VirtualMachineProfile.OsProfile.AdminPassword).Should(HaveLen(123))
431+
g.Expect(*vmss.VirtualMachineScaleSetProperties.VirtualMachineProfile.OsProfile.AdminUsername).Should(Equal("capi"))
432+
g.Expect(*vmss.VirtualMachineScaleSetProperties.VirtualMachineProfile.OsProfile.WindowsConfiguration.EnableAutomaticUpdates).Should(Equal(false))
433+
})
434+
m.Get(gomockinternal.AContext(), "my-rg", "my-vmss").
435+
Return(compute.VirtualMachineScaleSet{
436+
ID: to.StringPtr("vmss-id"),
437+
Name: to.StringPtr("my-vmss"),
438+
VirtualMachineScaleSetProperties: &compute.VirtualMachineScaleSetProperties{
439+
ProvisioningState: to.StringPtr("Succeeded"),
440+
},
441+
}, nil)
442+
m.ListInstances(gomockinternal.AContext(), "my-rg", "my-vmss").Return([]compute.VirtualMachineScaleSetVM{
443+
{
444+
InstanceID: to.StringPtr("id-2"),
445+
VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{
446+
ProvisioningState: to.StringPtr("Succeeded"),
447+
},
448+
ID: to.StringPtr("id-1"),
449+
Name: to.StringPtr("instance-0"),
450+
},
451+
}, nil)
452+
s.SaveK8sVersion()
453+
s.NeedsK8sVersionUpdate()
454+
s.UpdateInstanceStatuses(gomock.Any(), gomock.Len(1)).Return(nil)
455+
s.SetProviderID("azure://vmss-id")
456+
s.SetAnnotation("cluster-api-provider-azure", "true")
457+
s.SetProvisioningState(infrav1.VMStateSucceeded)
458+
},
459+
},
386460
{
387461
name: "with accelerated networking enabled",
388462
expectedError: "",

cloud/services/virtualmachines/virtualmachines.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func (s *Service) generateOSProfile(ctx context.Context, vmSpec azure.VMSpec) (*
433433
}
434434

435435
switch vmSpec.OSDisk.OSType {
436-
case azure.WindowsOS:
436+
case string(compute.Windows):
437437
// Cloudbase-init is used to generate a password.
438438
// https://cloudbase-init.readthedocs.io/en/latest/plugins.html#setting-password-main
439439
//

0 commit comments

Comments
 (0)