sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/availabilitysets/availabilitysets_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package availabilitysets
    18  
    19  import (
    20  	"context"
    21  	"io"
    22  	"net/http"
    23  	"strconv"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
    28  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    29  	. "github.com/onsi/gomega"
    30  	"github.com/pkg/errors"
    31  	"go.uber.org/mock/gomock"
    32  	"k8s.io/utils/ptr"
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async"
    35  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/availabilitysets/mock_availabilitysets"
    36  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
    37  	gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
    38  	"sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    39  )
    40  
    41  var (
    42  	fakeFaultDomainCount = 3
    43  	fakeSku              = resourceskus.SKU{
    44  		Capabilities: []*armcompute.ResourceSKUCapabilities{
    45  			{
    46  				Name:  ptr.To(resourceskus.MaximumPlatformFaultDomainCount),
    47  				Value: ptr.To(strconv.Itoa(fakeFaultDomainCount)),
    48  			},
    49  		},
    50  	}
    51  	fakeSetSpec = AvailabilitySetSpec{
    52  		Name:           "test-as",
    53  		ResourceGroup:  "test-rg",
    54  		ClusterName:    "test-cluster",
    55  		Location:       "test-location",
    56  		SKU:            &fakeSku,
    57  		AdditionalTags: map[string]string{},
    58  	}
    59  	fakeSetSpecMissing = AvailabilitySetSpec{
    60  		Name:           "test-as",
    61  		ResourceGroup:  "test-rg",
    62  		ClusterName:    "test-cluster",
    63  		Location:       "test-location",
    64  		SKU:            nil,
    65  		AdditionalTags: map[string]string{},
    66  	}
    67  	parameterError = errors.Errorf("some error with parameters")
    68  	notFoundError  = &azcore.ResponseError{StatusCode: http.StatusNotFound}
    69  	fakeSetWithVMs = armcompute.AvailabilitySet{
    70  		Properties: &armcompute.AvailabilitySetProperties{
    71  			VirtualMachines: []*armcompute.SubResource{
    72  				{ID: ptr.To("vm-id")},
    73  			},
    74  		},
    75  	}
    76  )
    77  
    78  func internalError() *azcore.ResponseError {
    79  	return &azcore.ResponseError{
    80  		RawResponse: &http.Response{
    81  			Body:       io.NopCloser(strings.NewReader("#: Internal Server Error: StatusCode=500")),
    82  			StatusCode: http.StatusInternalServerError,
    83  		},
    84  	}
    85  }
    86  
    87  func TestReconcileAvailabilitySets(t *testing.T) {
    88  	testcases := []struct {
    89  		name          string
    90  		expectedError string
    91  		expect        func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder)
    92  	}{
    93  		{
    94  			name:          "create or update availability set",
    95  			expectedError: "",
    96  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
    97  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
    98  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
    99  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeSetSpec, serviceName).Return(nil, nil)
   100  				s.UpdatePutStatus(infrav1.AvailabilitySetReadyCondition, serviceName, nil)
   101  			},
   102  		},
   103  		{
   104  			name:          "noop if no availability set spec returns nil",
   105  			expectedError: "",
   106  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   107  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   108  				s.AvailabilitySetSpec().Return(nil)
   109  			},
   110  		},
   111  		{
   112  			name:          "missing required value in availability set spec",
   113  			expectedError: "some error with parameters",
   114  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   115  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   116  				s.AvailabilitySetSpec().Return(&fakeSetSpecMissing)
   117  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeSetSpecMissing, serviceName).Return(nil, parameterError)
   118  				s.UpdatePutStatus(infrav1.AvailabilitySetReadyCondition, serviceName, parameterError)
   119  			},
   120  		},
   121  		{
   122  			name:          "error in creating availability set",
   123  			expectedError: "#: Internal Server Error: StatusCode=500",
   124  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   125  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   126  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   127  				r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeSetSpec, serviceName).Return(nil, internalError())
   128  				s.UpdatePutStatus(infrav1.AvailabilitySetReadyCondition, serviceName, internalError())
   129  			},
   130  		},
   131  	}
   132  	for _, tc := range testcases {
   133  		tc := tc
   134  		t.Run(tc.name, func(t *testing.T) {
   135  			g := NewWithT(t)
   136  
   137  			t.Parallel()
   138  			mockCtrl := gomock.NewController(t)
   139  			defer mockCtrl.Finish()
   140  			scopeMock := mock_availabilitysets.NewMockAvailabilitySetScope(mockCtrl)
   141  			asyncMock := mock_async.NewMockReconciler(mockCtrl)
   142  
   143  			tc.expect(scopeMock.EXPECT(), asyncMock.EXPECT())
   144  
   145  			s := &Service{
   146  				Scope:      scopeMock,
   147  				Reconciler: asyncMock,
   148  			}
   149  
   150  			err := s.Reconcile(context.TODO())
   151  			if tc.expectedError != "" {
   152  				g.Expect(err).To(HaveOccurred())
   153  				g.Expect(err.Error()).To(ContainSubstring(tc.expectedError))
   154  			} else {
   155  				g.Expect(err).NotTo(HaveOccurred())
   156  			}
   157  		})
   158  	}
   159  }
   160  
   161  func TestDeleteAvailabilitySets(t *testing.T) {
   162  	testcases := []struct {
   163  		name          string
   164  		expectedError string
   165  		expect        func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder)
   166  	}{
   167  		{
   168  			name:          "deletes availability set",
   169  			expectedError: "",
   170  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   171  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   172  				gomock.InOrder(
   173  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   174  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return(armcompute.AvailabilitySet{}, nil),
   175  					r.DeleteResource(gomockinternal.AContext(), &fakeSetSpec, serviceName).Return(nil),
   176  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, nil),
   177  				)
   178  			},
   179  		},
   180  		{
   181  			name:          "noop if AvailabilitySetSpec returns nil",
   182  			expectedError: "",
   183  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   184  				s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout)
   185  				s.AvailabilitySetSpec().Return(nil)
   186  			},
   187  		},
   188  		{
   189  			name:          "delete proceeds with missing required value in availability set spec",
   190  			expectedError: "",
   191  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   192  				s.AvailabilitySetSpec().Return(&fakeSetSpecMissing)
   193  				gomock.InOrder(
   194  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   195  					m.Get(gomockinternal.AContext(), &fakeSetSpecMissing).Return(armcompute.AvailabilitySet{}, nil),
   196  					r.DeleteResource(gomockinternal.AContext(), &fakeSetSpecMissing, serviceName).Return(nil),
   197  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, nil),
   198  				)
   199  			},
   200  		},
   201  		{
   202  			name:          "noop if availability set has vms",
   203  			expectedError: "",
   204  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   205  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   206  				gomock.InOrder(
   207  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   208  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return(fakeSetWithVMs, nil),
   209  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, nil),
   210  				)
   211  			},
   212  		},
   213  		{
   214  			name:          "availability set not found",
   215  			expectedError: "",
   216  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   217  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   218  				gomock.InOrder(
   219  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   220  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return(nil, notFoundError),
   221  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, nil),
   222  				)
   223  			},
   224  		},
   225  		{
   226  			name:          "error in getting availability set",
   227  			expectedError: "failed to get availability set test-as in resource group test-rg:.*#: Internal Server Error: StatusCode=500",
   228  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   229  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   230  				gomock.InOrder(
   231  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   232  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return(nil, internalError()),
   233  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, gomockinternal.ErrStrEq("failed to get availability set test-as in resource group test-rg: "+internalError().Error())),
   234  				)
   235  			},
   236  		},
   237  		{
   238  			name:          "availability set get result is not an availability set",
   239  			expectedError: "string is not an armcompute.AvailabilitySet",
   240  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   241  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   242  				gomock.InOrder(
   243  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   244  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return("not an availability set", nil),
   245  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, gomockinternal.ErrStrEq("string is not an armcompute.AvailabilitySet")),
   246  				)
   247  			},
   248  		},
   249  		{
   250  			name:          "error in deleting availability set",
   251  			expectedError: "#: Internal Server Error: StatusCode=500",
   252  			expect: func(s *mock_availabilitysets.MockAvailabilitySetScopeMockRecorder, m *mock_async.MockGetterMockRecorder, r *mock_async.MockReconcilerMockRecorder) {
   253  				s.AvailabilitySetSpec().Return(&fakeSetSpec)
   254  				gomock.InOrder(
   255  					s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout),
   256  					m.Get(gomockinternal.AContext(), &fakeSetSpec).Return(armcompute.AvailabilitySet{}, nil),
   257  					r.DeleteResource(gomockinternal.AContext(), &fakeSetSpec, serviceName).Return(internalError()),
   258  					s.UpdateDeleteStatus(infrav1.AvailabilitySetReadyCondition, serviceName, internalError()),
   259  				)
   260  			},
   261  		},
   262  	}
   263  	for _, tc := range testcases {
   264  		tc := tc
   265  		t.Run(tc.name, func(t *testing.T) {
   266  			g := NewWithT(t)
   267  
   268  			t.Parallel()
   269  			mockCtrl := gomock.NewController(t)
   270  			defer mockCtrl.Finish()
   271  			scopeMock := mock_availabilitysets.NewMockAvailabilitySetScope(mockCtrl)
   272  			getterMock := mock_async.NewMockGetter(mockCtrl)
   273  			asyncMock := mock_async.NewMockReconciler(mockCtrl)
   274  
   275  			tc.expect(scopeMock.EXPECT(), getterMock.EXPECT(), asyncMock.EXPECT())
   276  
   277  			s := &Service{
   278  				Scope:      scopeMock,
   279  				Getter:     getterMock,
   280  				Reconciler: asyncMock,
   281  			}
   282  
   283  			err := s.Delete(context.TODO())
   284  			if tc.expectedError != "" {
   285  				g.Expect(err).To(HaveOccurred())
   286  				g.Expect(strings.ReplaceAll(err.Error(), "\n", "")).To(MatchRegexp(tc.expectedError))
   287  			} else {
   288  				g.Expect(err).NotTo(HaveOccurred())
   289  			}
   290  		})
   291  	}
   292  }