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 }