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  }