sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/securitygroups/securitygroups_test.go (about) 1 /* 2 Copyright 2019 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 securitygroups 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 24 . "github.com/onsi/gomega" 25 "github.com/pkg/errors" 26 "go.uber.org/mock/gomock" 27 "k8s.io/utils/ptr" 28 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 29 "sigs.k8s.io/cluster-api-provider-azure/azure" 30 "sigs.k8s.io/cluster-api-provider-azure/azure/services/async/mock_async" 31 "sigs.k8s.io/cluster-api-provider-azure/azure/services/securitygroups/mock_securitygroups" 32 gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock" 33 "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 34 ) 35 36 var ( 37 annotation = azure.SecurityRuleLastAppliedAnnotation 38 fakeNSG = NSGSpec{ 39 Name: "test-nsg", 40 Location: "test-location", 41 ClusterName: "my-cluster", 42 SecurityRules: infrav1.SecurityRules{ 43 securityRule1, 44 }, 45 ResourceGroup: "test-group", 46 } 47 noRulesNSG = NSGSpec{ 48 Name: "test-nsg-2", 49 Location: "test-location", 50 ClusterName: "my-cluster", 51 SecurityRules: infrav1.SecurityRules{}, 52 ResourceGroup: "test-group", 53 } 54 multipleRulesNSG = NSGSpec{ 55 Name: "multiple-rules-nsg", 56 Location: "test-location", 57 ClusterName: "my-cluster", 58 SecurityRules: infrav1.SecurityRules{ 59 securityRule1, 60 securityRule2, 61 }, 62 ResourceGroup: "test-group", 63 } 64 securityRule1 = infrav1.SecurityRule{ 65 Name: "allow_ssh", 66 Description: "Allow SSH", 67 Priority: 2200, 68 Protocol: infrav1.SecurityGroupProtocolTCP, 69 Direction: infrav1.SecurityRuleDirectionInbound, 70 Source: ptr.To("*"), 71 SourcePorts: ptr.To("*"), 72 Destination: ptr.To("*"), 73 DestinationPorts: ptr.To("22"), 74 Action: infrav1.SecurityRuleActionAllow, 75 } 76 securityRule2 = infrav1.SecurityRule{ 77 Name: "other_rule", 78 Description: "Test Rule", 79 Priority: 500, 80 Protocol: infrav1.SecurityGroupProtocolTCP, 81 Direction: infrav1.SecurityRuleDirectionInbound, 82 Source: ptr.To("*"), 83 SourcePorts: ptr.To("*"), 84 Destination: ptr.To("*"), 85 DestinationPorts: ptr.To("80"), 86 Action: infrav1.SecurityRuleActionAllow, 87 } 88 errFake = errors.New("this is an error") 89 notDoneError = azure.NewOperationNotDoneError(&infrav1.Future{}) 90 ) 91 92 func TestReconcileSecurityGroups(t *testing.T) { 93 testcases := []struct { 94 name string 95 expectedError string 96 expect func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) 97 }{ 98 { 99 name: "create single security group with single rule succeeds, should return no error", 100 expectedError: "", 101 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 102 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 103 s.IsVnetManaged().Return(true) 104 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG}) 105 s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1) 106 r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, nil) 107 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil) 108 }, 109 }, 110 { 111 name: "create single security group with multiple rules succeeds, should return no error", 112 expectedError: "", 113 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 114 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 115 s.IsVnetManaged().Return(true) 116 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&multipleRulesNSG}) 117 s.UpdateAnnotationJSON(annotation, map[string]interface{}{multipleRulesNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description, securityRule2.Name: securityRule2.Description}}).Times(1) 118 r.CreateOrUpdateResource(gomockinternal.AContext(), &multipleRulesNSG, serviceName).Return(nil, nil) 119 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil) 120 }, 121 }, 122 { 123 name: "create multiple security groups, should return no error", 124 expectedError: "", 125 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 126 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 127 s.IsVnetManaged().Return(true) 128 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 129 s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1) 130 r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, nil) 131 r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, nil) 132 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil) 133 }, 134 }, 135 { 136 name: "first security groups create fails, should return error", 137 expectedError: errFake.Error(), 138 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 139 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 140 s.IsVnetManaged().Return(true) 141 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 142 s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1) 143 r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, errFake) 144 r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, nil) 145 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake) 146 }, 147 }, 148 { 149 name: "first sg create fails, second sg create not done, should return create error", 150 expectedError: errFake.Error(), 151 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 152 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 153 s.IsVnetManaged().Return(true) 154 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 155 s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}).Times(1) 156 r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, errFake) 157 r.CreateOrUpdateResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil, notDoneError) 158 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake) 159 }, 160 }, 161 { 162 name: "security groups create not done, should return not done error", 163 expectedError: notDoneError.Error(), 164 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 165 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 166 s.IsVnetManaged().Return(true) 167 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG}) 168 s.UpdateAnnotationJSON(annotation, map[string]interface{}{fakeNSG.Name: map[string]string{securityRule1.Name: securityRule1.Description}}) 169 r.CreateOrUpdateResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil, notDoneError) 170 s.UpdatePutStatus(infrav1.SecurityGroupsReadyCondition, serviceName, notDoneError) 171 }, 172 }, 173 { 174 name: "vnet is not managed, should skip reconcile", 175 expectedError: "", 176 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 177 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 178 s.IsVnetManaged().Return(false) 179 }, 180 }, 181 } 182 for _, tc := range testcases { 183 tc := tc 184 t.Run(tc.name, func(t *testing.T) { 185 g := NewWithT(t) 186 t.Parallel() 187 mockCtrl := gomock.NewController(t) 188 defer mockCtrl.Finish() 189 190 scopeMock := mock_securitygroups.NewMockNSGScope(mockCtrl) 191 reconcilerMock := mock_async.NewMockReconciler(mockCtrl) 192 193 tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT()) 194 195 s := &Service{ 196 Scope: scopeMock, 197 Reconciler: reconcilerMock, 198 } 199 200 err := s.Reconcile(context.TODO()) 201 if tc.expectedError != "" { 202 g.Expect(err).To(HaveOccurred()) 203 g.Expect(err).To(MatchError(tc.expectedError)) 204 } else { 205 g.Expect(err).NotTo(HaveOccurred()) 206 } 207 }) 208 } 209 } 210 211 func TestDeleteSecurityGroups(t *testing.T) { 212 testcases := []struct { 213 name string 214 expectedError string 215 expect func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) 216 }{ 217 { 218 name: "delete multiple security groups succeeds, should return no error", 219 expectedError: "", 220 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 221 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 222 s.IsVnetManaged().Return(true) 223 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 224 r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(nil) 225 r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil) 226 s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, nil) 227 }, 228 }, 229 { 230 name: "first security groups delete fails, should return an error", 231 expectedError: errFake.Error(), 232 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 233 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 234 s.IsVnetManaged().Return(true) 235 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 236 r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(errFake) 237 r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(nil) 238 s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake) 239 }, 240 }, 241 { 242 name: "first security groups delete fails and second security groups create not done, should return an error", 243 expectedError: errFake.Error(), 244 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 245 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 246 s.IsVnetManaged().Return(true) 247 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG, &noRulesNSG}) 248 r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(errFake) 249 r.DeleteResource(gomockinternal.AContext(), &noRulesNSG, serviceName).Return(notDoneError) 250 s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, errFake) 251 }, 252 }, 253 { 254 name: "security groups delete not done, should return not done error", 255 expectedError: notDoneError.Error(), 256 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 257 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 258 s.IsVnetManaged().Return(true) 259 s.NSGSpecs().Return([]azure.ResourceSpecGetter{&fakeNSG}) 260 r.DeleteResource(gomockinternal.AContext(), &fakeNSG, serviceName).Return(notDoneError) 261 s.UpdateDeleteStatus(infrav1.SecurityGroupsReadyCondition, serviceName, notDoneError) 262 }, 263 }, 264 { 265 name: "vnet is not managed, should skip delete", 266 expectedError: "", 267 expect: func(s *mock_securitygroups.MockNSGScopeMockRecorder, r *mock_async.MockReconcilerMockRecorder) { 268 s.DefaultedAzureServiceReconcileTimeout().Return(reconciler.DefaultAzureServiceReconcileTimeout) 269 s.IsVnetManaged().Return(false) 270 }, 271 }, 272 } 273 for _, tc := range testcases { 274 tc := tc 275 t.Run(tc.name, func(t *testing.T) { 276 g := NewWithT(t) 277 t.Parallel() 278 mockCtrl := gomock.NewController(t) 279 defer mockCtrl.Finish() 280 281 scopeMock := mock_securitygroups.NewMockNSGScope(mockCtrl) 282 reconcilerMock := mock_async.NewMockReconciler(mockCtrl) 283 284 tc.expect(scopeMock.EXPECT(), reconcilerMock.EXPECT()) 285 286 s := &Service{ 287 Scope: scopeMock, 288 Reconciler: reconcilerMock, 289 } 290 291 err := s.Delete(context.TODO()) 292 if tc.expectedError != "" { 293 g.Expect(err).To(HaveOccurred()) 294 g.Expect(err).To(MatchError(tc.expectedError)) 295 } else { 296 g.Expect(err).NotTo(HaveOccurred()) 297 } 298 }) 299 } 300 } 301 302 var ( 303 ruleA = &armnetwork.SecurityRule{ 304 Name: ptr.To("A"), 305 Properties: &armnetwork.SecurityRulePropertiesFormat{ 306 Description: ptr.To("this is rule A"), 307 Protocol: ptr.To(armnetwork.SecurityRuleProtocolTCP), 308 DestinationPortRange: ptr.To("*"), 309 SourcePortRange: ptr.To("*"), 310 DestinationAddressPrefix: ptr.To("*"), 311 SourceAddressPrefix: ptr.To("*"), 312 Priority: ptr.To[int32](100), 313 Direction: ptr.To(armnetwork.SecurityRuleDirectionInbound), 314 Access: ptr.To(armnetwork.SecurityRuleAccessAllow), 315 }, 316 } 317 ruleB = &armnetwork.SecurityRule{ 318 Name: ptr.To("B"), 319 Properties: &armnetwork.SecurityRulePropertiesFormat{ 320 Description: ptr.To("this is rule B"), 321 Protocol: ptr.To(armnetwork.SecurityRuleProtocolTCP), 322 DestinationPortRange: ptr.To("*"), 323 SourcePortRange: ptr.To("*"), 324 DestinationAddressPrefix: ptr.To("*"), 325 SourceAddressPrefix: ptr.To("*"), 326 Priority: ptr.To[int32](100), 327 Direction: ptr.To(armnetwork.SecurityRuleDirectionOutbound), 328 Access: ptr.To(armnetwork.SecurityRuleAccessAllow), 329 }, 330 } 331 ruleBModified = &armnetwork.SecurityRule{ 332 Name: ptr.To("B"), 333 Properties: &armnetwork.SecurityRulePropertiesFormat{ 334 Description: ptr.To("this is rule B"), 335 Protocol: ptr.To(armnetwork.SecurityRuleProtocolTCP), 336 DestinationPortRange: ptr.To("80"), 337 SourcePortRange: ptr.To("*"), 338 DestinationAddressPrefix: ptr.To("*"), 339 SourceAddressPrefix: ptr.To("*"), 340 Priority: ptr.To[int32](100), 341 Direction: ptr.To(armnetwork.SecurityRuleDirectionOutbound), 342 Access: ptr.To(armnetwork.SecurityRuleAccessAllow), 343 }, 344 } 345 )