sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/inboundnatrules/inboundnatrules_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 inboundnatrules 18 19 import ( 20 "context" 21 "io" 22 "net/http" 23 "strings" 24 "testing" 25 26 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 27 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 28 . "github.com/onsi/gomega" 29 "go.uber.org/mock/gomock" 30 "k8s.io/utils/ptr" 31 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 32 "sigs.k8s.io/cluster-api-provider-azure/azure" 33 "sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async" 34 "sigs.k8s.io/cluster-api-provider-azure/azure/services/inboundnatrules/mock_inboundnatrules" 35 gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock" 36 "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 37 ) 38 39 var ( 40 fakeLBName = "my-lb-1" 41 fakeGroupName = "my-rg" 42 43 noExistingRules = []armnetwork.InboundNatRule{} 44 fakeExistingRules = []armnetwork.InboundNatRule{ 45 { 46 Name: ptr.To("other-machine-nat-rule"), 47 ID: ptr.To("some-natrules-id"), 48 Properties: &armnetwork.InboundNatRulePropertiesFormat{ 49 FrontendPort: ptr.To[int32](22), 50 }, 51 }, 52 { 53 Name: ptr.To("other-machine-nat-rule-2"), 54 ID: ptr.To("some-natrules-id-2"), 55 Properties: &armnetwork.InboundNatRulePropertiesFormat{ 56 FrontendPort: ptr.To[int32](2201), 57 }, 58 }, 59 } 60 61 fakeNatSpec = InboundNatSpec{ 62 Name: "my-machine-1", 63 LoadBalancerName: "my-lb-1", 64 ResourceGroup: fakeGroupName, 65 FrontendIPConfigurationID: ptr.To("frontend-ip-config-id-1"), 66 } 67 fakeNatSpec2 = InboundNatSpec{ 68 Name: "my-machine-2", 69 LoadBalancerName: "my-lb-1", 70 ResourceGroup: fakeGroupName, 71 FrontendIPConfigurationID: ptr.To("frontend-ip-config-id-2"), 72 } 73 74 internalError = &azcore.ResponseError{ 75 RawResponse: &http.Response{ 76 Body: io.NopCloser(strings.NewReader("#: Internal Server Error: StatusCode=500")), 77 StatusCode: http.StatusInternalServerError, 78 }, 79 } 80 ) 81 82 func getFakeNatSpecWithoutPort(spec InboundNatSpec) *InboundNatSpec { 83 newSpec := spec 84 return &newSpec 85 } 86 87 func getFakeNatSpecWithPort(spec InboundNatSpec, port int32) *InboundNatSpec { 88 newSpec := spec 89 newSpec.SSHFrontendPort = ptr.To[int32](port) 90 return &newSpec 91 } 92 93 func TestReconcileInboundNATRule(t *testing.T) { 94 testcases := []struct { 95 name string 96 expectedError string 97 expect func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 98 m *mock_inboundnatrules.MockclientMockRecorder, 99 r *mock_async.MockReconcilerMockRecorder) 100 }{ 101 { 102 name: "noop if no NAT rule specs are found", 103 expectedError: "", 104 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 105 m *mock_inboundnatrules.MockclientMockRecorder, 106 r *mock_async.MockReconcilerMockRecorder) { 107 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 108 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 109 s.APIServerLBName().AnyTimes().Return(fakeLBName) 110 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{}) 111 }, 112 }, 113 { 114 name: "NAT rule successfully created with no existing rules", 115 expectedError: "", 116 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 117 m *mock_inboundnatrules.MockclientMockRecorder, 118 r *mock_async.MockReconcilerMockRecorder) { 119 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 120 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 121 s.APIServerLBName().AnyTimes().Return(fakeLBName) 122 m.List(gomockinternal.AContext(), fakeGroupName, fakeLBName).Return(noExistingRules, nil) 123 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{getFakeNatSpecWithoutPort(fakeNatSpec), getFakeNatSpecWithoutPort(fakeNatSpec2)}) 124 gomock.InOrder( 125 r.CreateOrUpdateResource(gomockinternal.AContext(), getFakeNatSpecWithPort(fakeNatSpec, 22), serviceName).Return(nil, nil), 126 r.CreateOrUpdateResource(gomockinternal.AContext(), getFakeNatSpecWithPort(fakeNatSpec2, 2201), serviceName).Return(nil, nil), 127 s.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, nil), 128 ) 129 }, 130 }, 131 { 132 name: "NAT rule successfully created with existing rules", 133 expectedError: "", 134 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 135 m *mock_inboundnatrules.MockclientMockRecorder, 136 r *mock_async.MockReconcilerMockRecorder) { 137 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 138 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 139 s.APIServerLBName().AnyTimes().Return("my-lb") 140 m.List(gomockinternal.AContext(), fakeGroupName, "my-lb").Return(fakeExistingRules, nil) 141 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{getFakeNatSpecWithoutPort(fakeNatSpec)}) 142 gomock.InOrder( 143 r.CreateOrUpdateResource(gomockinternal.AContext(), getFakeNatSpecWithPort(fakeNatSpec, 2202), serviceName).Return(nil, nil), 144 s.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, nil), 145 ) 146 }, 147 }, 148 { 149 name: "No LB, Nat rule reconciliation is skipped", 150 expectedError: "", 151 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 152 m *mock_inboundnatrules.MockclientMockRecorder, 153 r *mock_async.MockReconcilerMockRecorder) { 154 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 155 s.APIServerLBName().AnyTimes().Return("") 156 }, 157 }, 158 { 159 name: "fail to get existing rules", 160 expectedError: `failed to get existing NAT rules:.*#: Internal Server Error: StatusCode=500`, 161 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 162 m *mock_inboundnatrules.MockclientMockRecorder, 163 r *mock_async.MockReconcilerMockRecorder) { 164 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 165 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 166 s.APIServerLBName().AnyTimes().Return("my-lb") 167 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{&fakeNatSpec}) 168 m.List(gomockinternal.AContext(), fakeGroupName, "my-lb").Return(nil, internalError) 169 s.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, gomockinternal.ErrStrEq("failed to get existing NAT rules: "+internalError.Error())) 170 }, 171 }, 172 { 173 name: "fail to create NAT rule", 174 expectedError: "#: Internal Server Error: StatusCode=500", 175 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 176 m *mock_inboundnatrules.MockclientMockRecorder, 177 r *mock_async.MockReconcilerMockRecorder) { 178 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 179 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 180 s.APIServerLBName().AnyTimes().Return("my-lb") 181 m.List(gomockinternal.AContext(), fakeGroupName, "my-lb").Return(fakeExistingRules, nil) 182 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{&fakeNatSpec}) 183 gomock.InOrder( 184 r.CreateOrUpdateResource(gomockinternal.AContext(), getFakeNatSpecWithPort(fakeNatSpec, 2202), serviceName).Return(nil, internalError), 185 s.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, internalError), 186 ) 187 }, 188 }, 189 } 190 191 for _, tc := range testcases { 192 tc := tc 193 t.Run(tc.name, func(t *testing.T) { 194 g := NewWithT(t) 195 // TODO: investigate why t.Parallel() trips the race detector here. 196 mockCtrl := gomock.NewController(t) 197 defer mockCtrl.Finish() 198 scopeMock := mock_inboundnatrules.NewMockInboundNatScope(mockCtrl) 199 clientMock := mock_inboundnatrules.NewMockclient(mockCtrl) 200 asyncMock := mock_async.NewMockReconciler(mockCtrl) 201 202 tc.expect(scopeMock.EXPECT(), clientMock.EXPECT(), asyncMock.EXPECT()) 203 204 s := &Service{ 205 Scope: scopeMock, 206 client: clientMock, 207 Reconciler: asyncMock, 208 } 209 210 err := s.Reconcile(context.TODO()) 211 if tc.expectedError != "" { 212 g.Expect(err).To(HaveOccurred()) 213 g.Expect(strings.ReplaceAll(err.Error(), "\n", "")).To(MatchRegexp(tc.expectedError)) 214 } else { 215 g.Expect(err).NotTo(HaveOccurred()) 216 } 217 }) 218 } 219 } 220 221 func TestDeleteNetworkInterface(t *testing.T) { 222 testcases := []struct { 223 name string 224 expectedError string 225 expect func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 226 m *mock_inboundnatrules.MockclientMockRecorder, r *mock_async.MockReconcilerMockRecorder) 227 }{ 228 { 229 name: "noop if no NAT rules are found", 230 expectedError: "", 231 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 232 m *mock_inboundnatrules.MockclientMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 233 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 234 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{}) 235 }, 236 }, 237 { 238 name: "successfully delete an existing NAT rule", 239 expectedError: "", 240 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 241 m *mock_inboundnatrules.MockclientMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 242 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 243 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{&fakeNatSpec}) 244 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 245 s.APIServerLBName().AnyTimes().Return(fakeLBName) 246 gomock.InOrder( 247 r.DeleteResource(gomockinternal.AContext(), &fakeNatSpec, serviceName).Return(nil), 248 s.UpdateDeleteStatus(infrav1.InboundNATRulesReadyCondition, serviceName, nil), 249 ) 250 }, 251 }, 252 { 253 name: "NAT rule deletion fails", 254 expectedError: "#: Internal Server Error: StatusCode=500", 255 expect: func(s *mock_inboundnatrules.MockInboundNatScopeMockRecorder, 256 m *mock_inboundnatrules.MockclientMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 257 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 258 s.InboundNatSpecs().Return([]azure.ResourceSpecGetter{&fakeNatSpec}) 259 s.ResourceGroup().AnyTimes().Return(fakeGroupName) 260 s.APIServerLBName().AnyTimes().Return(fakeLBName) 261 gomock.InOrder( 262 r.DeleteResource(gomockinternal.AContext(), &fakeNatSpec, serviceName).Return(internalError), 263 s.UpdateDeleteStatus(infrav1.InboundNATRulesReadyCondition, serviceName, internalError), 264 ) 265 }, 266 }, 267 } 268 269 for _, tc := range testcases { 270 tc := tc 271 t.Run(tc.name, func(t *testing.T) { 272 g := NewWithT(t) 273 t.Parallel() 274 mockCtrl := gomock.NewController(t) 275 defer mockCtrl.Finish() 276 scopeMock := mock_inboundnatrules.NewMockInboundNatScope(mockCtrl) 277 clientMock := mock_inboundnatrules.NewMockclient(mockCtrl) 278 asyncMock := mock_async.NewMockReconciler(mockCtrl) 279 280 tc.expect(scopeMock.EXPECT(), clientMock.EXPECT(), asyncMock.EXPECT()) 281 282 s := &Service{ 283 Scope: scopeMock, 284 client: clientMock, 285 Reconciler: asyncMock, 286 } 287 288 err := s.Delete(context.TODO()) 289 if tc.expectedError != "" { 290 g.Expect(err).To(HaveOccurred()) 291 g.Expect(strings.ReplaceAll(err.Error(), "\n", "")).To(MatchRegexp(tc.expectedError)) 292 } else { 293 g.Expect(err).NotTo(HaveOccurred()) 294 } 295 }) 296 } 297 }