@@ -5,17 +5,22 @@ package deploy
55
66import (
77 "context"
8+ "errors"
89 "fmt"
910 "reflect"
1011 "testing"
12+ "time"
1113
1214 "github.com/sirupsen/logrus"
1315 "go.uber.org/mock/gomock"
1416
17+ mgmtcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-06-01/compute"
1518 mgmtfeatures "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-07-01/features"
19+ "github.com/Azure/go-autorest/autorest"
1620 "github.com/Azure/go-autorest/autorest/azure"
1721
1822 "github.com/Azure/ARO-RP/pkg/util/arm"
23+ mock_compute "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/compute"
1924 mock_features "github.com/Azure/ARO-RP/pkg/util/mocks/azureclient/mgmt/features"
2025 mock_vmsscleaner "github.com/Azure/ARO-RP/pkg/util/mocks/vmsscleaner"
2126 "github.com/Azure/ARO-RP/pkg/util/pointerutils"
@@ -379,3 +384,199 @@ func TestGetParameters(t *testing.T) {
379384 })
380385 }
381386}
387+
388+ func TestDisableAutomaticRepairsOnVMSS (t * testing.T ) {
389+ ctx := context .Background ()
390+ resourceGroupName := "rg"
391+ vmssName := "vmss"
392+
393+ for _ , tt := range []struct {
394+ name string
395+ updateErr error
396+ }{
397+ {
398+ name : "success" ,
399+ },
400+ {
401+ name : "azure error returns nil" ,
402+ updateErr : errors .New ("update failed" ),
403+ },
404+ } {
405+ t .Run (tt .name , func (t * testing.T ) {
406+ controller := gomock .NewController (t )
407+ defer controller .Finish ()
408+
409+ mockVMSS := mock_compute .NewMockVirtualMachineScaleSetsClient (controller )
410+
411+ mockVMSS .EXPECT ().UpdateAndWait (ctx , resourceGroupName , vmssName , gomock .AssignableToTypeOf (mgmtcompute.VirtualMachineScaleSetUpdate {})).DoAndReturn (
412+ func (_ context.Context , rg , name string , update mgmtcompute.VirtualMachineScaleSetUpdate ) error {
413+ if update .VirtualMachineScaleSetUpdateProperties == nil {
414+ t .Fatalf ("expected VirtualMachineScaleSetUpdateProperties to be set" )
415+ }
416+ policy := update .AutomaticRepairsPolicy
417+ if policy == nil || policy .Enabled == nil {
418+ t .Fatalf ("expected AutomaticRepairsPolicy.Enabled to be set" )
419+ }
420+ if * policy .Enabled {
421+ t .Fatalf ("expected AutomaticRepairsPolicy.Enabled to be false" )
422+ }
423+ return tt .updateErr
424+ },
425+ )
426+
427+ d := deployer {
428+ log : logrus .NewEntry (logrus .StandardLogger ()),
429+ vmss : mockVMSS ,
430+ }
431+
432+ if err := d .disableAutomaticRepairsOnVMSS (ctx , resourceGroupName , vmssName ); err != nil {
433+ t .Fatalf ("unexpected error: %v" , err )
434+ }
435+ })
436+ }
437+ }
438+
439+ func TestRunCommandWithRetrySuccess (t * testing.T ) {
440+ ctx := context .Background ()
441+ controller := gomock .NewController (t )
442+ defer controller .Finish ()
443+
444+ input := mgmtcompute.RunCommandInput {}
445+ resourceGroupName := "rg"
446+ vmssName := "vmss"
447+ instanceID := "1"
448+
449+ mockVMSSVMs := mock_compute .NewMockVirtualMachineScaleSetVMsClient (controller )
450+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).Return (nil )
451+
452+ d := deployer {
453+ log : logrus .NewEntry (logrus .StandardLogger ()),
454+ vmssvms : mockVMSSVMs ,
455+ }
456+
457+ if err := d .runCommandWithRetry (ctx , resourceGroupName , vmssName , instanceID , input ); err != nil {
458+ t .Fatalf ("unexpected error: %v" , err )
459+ }
460+ }
461+
462+ func TestRunCommandWithRetryRetriesOnOperationPreempted (t * testing.T ) {
463+ ctx := context .Background ()
464+ controller := gomock .NewController (t )
465+ defer controller .Finish ()
466+
467+ input := mgmtcompute.RunCommandInput {}
468+ resourceGroupName := "rg"
469+ vmssName := "vmss"
470+ instanceID := "1"
471+
472+ mockVMSSVMs := mock_compute .NewMockVirtualMachineScaleSetVMsClient (controller )
473+ gomock .InOrder (
474+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).Return (newOperationPreemptedError ()),
475+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).Return (nil ),
476+ )
477+
478+ d := deployer {
479+ log : logrus .NewEntry (logrus .StandardLogger ()),
480+ vmssvms : mockVMSSVMs ,
481+ }
482+
483+ start := time .Now ()
484+ if err := d .runCommandWithRetry (ctx , resourceGroupName , vmssName , instanceID , input ); err != nil {
485+ t .Fatalf ("unexpected error: %v" , err )
486+ }
487+ duration := time .Since (start )
488+ if duration < 10 * time .Second {
489+ t .Fatalf ("expected retry delay of at least 10s, got %v" , duration )
490+ }
491+ }
492+
493+ func TestRunCommandWithRetryReturnsLastError (t * testing.T ) {
494+ ctx := context .Background ()
495+ controller := gomock .NewController (t )
496+ defer controller .Finish ()
497+
498+ input := mgmtcompute.RunCommandInput {}
499+ resourceGroupName := "rg"
500+ vmssName := "vmss"
501+ instanceID := "1"
502+ wantErr := newOperationPreemptedError ()
503+
504+ mockVMSSVMs := mock_compute .NewMockVirtualMachineScaleSetVMsClient (controller )
505+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).Return (wantErr ).Times (3 )
506+
507+ d := deployer {
508+ log : logrus .NewEntry (logrus .StandardLogger ()),
509+ vmssvms : mockVMSSVMs ,
510+ }
511+
512+ start := time .Now ()
513+ err := d .runCommandWithRetry (ctx , resourceGroupName , vmssName , instanceID , input )
514+ duration := time .Since (start )
515+ if err != wantErr {
516+ t .Fatalf ("expected %v, got %v" , wantErr , err )
517+ }
518+ if duration < 20 * time .Second {
519+ t .Fatalf ("expected retry delay of at least 20s (2 retries), got %v" , duration )
520+ }
521+ }
522+
523+ func TestRunCommandWithRetryReturnsNonRetryableError (t * testing.T ) {
524+ ctx := context .Background ()
525+ controller := gomock .NewController (t )
526+ defer controller .Finish ()
527+
528+ input := mgmtcompute.RunCommandInput {}
529+ resourceGroupName := "rg"
530+ vmssName := "vmss"
531+ instanceID := "1"
532+ wantErr := errors .New ("boom" )
533+
534+ mockVMSSVMs := mock_compute .NewMockVirtualMachineScaleSetVMsClient (controller )
535+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).Return (wantErr )
536+
537+ d := deployer {
538+ log : logrus .NewEntry (logrus .StandardLogger ()),
539+ vmssvms : mockVMSSVMs ,
540+ }
541+
542+ err := d .runCommandWithRetry (ctx , resourceGroupName , vmssName , instanceID , input )
543+ if err != wantErr {
544+ t .Fatalf ("expected %v, got %v" , wantErr , err )
545+ }
546+ }
547+
548+ func TestRunCommandWithRetryContextCancelled (t * testing.T ) {
549+ ctx , cancel := context .WithCancel (context .Background ())
550+ controller := gomock .NewController (t )
551+ defer controller .Finish ()
552+
553+ input := mgmtcompute.RunCommandInput {}
554+ resourceGroupName := "rg"
555+ vmssName := "vmss"
556+ instanceID := "1"
557+
558+ mockVMSSVMs := mock_compute .NewMockVirtualMachineScaleSetVMsClient (controller )
559+ mockVMSSVMs .EXPECT ().RunCommandAndWait (ctx , resourceGroupName , vmssName , instanceID , input ).DoAndReturn (
560+ func (context.Context , string , string , string , mgmtcompute.RunCommandInput ) error {
561+ // Cancel context during the first call to simulate cancellation during retry
562+ cancel ()
563+ return newOperationPreemptedError ()
564+ },
565+ )
566+
567+ d := deployer {
568+ log : logrus .NewEntry (logrus .StandardLogger ()),
569+ vmssvms : mockVMSSVMs ,
570+ }
571+
572+ err := d .runCommandWithRetry (ctx , resourceGroupName , vmssName , instanceID , input )
573+ if ! errors .Is (err , context .Canceled ) {
574+ t .Fatalf ("expected context.Canceled error, got %v" , err )
575+ }
576+ }
577+
578+ func newOperationPreemptedError () error {
579+ return & autorest.DetailedError {
580+ Original : & azure.ServiceError {Code : OperationPreemptedCode },
581+ }
582+ }
0 commit comments