Skip to content

Commit a8c7349

Browse files
authored
Merge pull request #451 from CecileRobertMichon/natrulesfix-2
🐛 make NAT Rules creation and deletion owned by network interfaces
2 parents 39bf9ef + 8333398 commit a8c7349

File tree

11 files changed

+637
-169
lines changed

11 files changed

+637
-169
lines changed

api/v1alpha2/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ type LoadBalancer struct {
262262
Probes *[]Probe `json:"probes,omitempty"`
263263
// InboundNatRules - Collection of inbound NAT Rules used by a load balancer. Defining inbound NAT rules on your load balancer is mutually exclusive with defining an inbound NAT pool. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an Inbound NAT pool. They have to reference individual inbound NAT rules.
264264
InboundNatRules *[]InboundNatRule `json:"inboundNatRules,omitempty"`
265-
// InboundNatPools - Defines an external port range for inbound NAT to a single backend port on NICs associated with a load balancer. Inbound NAT rules are created automatically for each NIC associated with the Load Balancer using an external port from this range. Defining an Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound Nat rules. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an inbound NAT pool. They have to reference individual inbound NAT rules.
265+
// InboundNatPools - Defines an external port range for inbound NAT to a single backend port on NICs associated with a load balancer. Inbound NAT rules are created automatically for each NIC associated with the Load Balancer using an external port from this range. Defining an Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound NAT rules. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an inbound NAT pool. They have to reference individual inbound NAT rules.
266266
InboundNatPools *[]InboundNatPool `json:"inboundNatPools,omitempty"`
267267
// OutboundRules - The outbound rules.
268268
OutboundRules *[]OutboundRule `json:"outboundRules,omitempty"`

api/v1alpha3/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ type LoadBalancer struct {
262262
Probes *[]Probe `json:"probes,omitempty"`
263263
// InboundNatRules - Collection of inbound NAT Rules used by a load balancer. Defining inbound NAT rules on your load balancer is mutually exclusive with defining an inbound NAT pool. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an Inbound NAT pool. They have to reference individual inbound NAT rules.
264264
InboundNatRules *[]InboundNatRule `json:"inboundNatRules,omitempty"`
265-
// InboundNatPools - Defines an external port range for inbound NAT to a single backend port on NICs associated with a load balancer. Inbound NAT rules are created automatically for each NIC associated with the Load Balancer using an external port from this range. Defining an Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound Nat rules. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an inbound NAT pool. They have to reference individual inbound NAT rules.
265+
// InboundNatPools - Defines an external port range for inbound NAT to a single backend port on NICs associated with a load balancer. Inbound NAT rules are created automatically for each NIC associated with the Load Balancer using an external port from this range. Defining an Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound NAT rules. Inbound NAT pools are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an inbound NAT pool. They have to reference individual inbound NAT rules.
266266
InboundNatPools *[]InboundNatPool `json:"inboundNatPools,omitempty"`
267267
// OutboundRules - The outbound rules.
268268
OutboundRules *[]OutboundRule `json:"outboundRules,omitempty"`
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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 inboundnatrules
18+
19+
import (
20+
"context"
21+
22+
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
23+
"github.com/Azure/go-autorest/autorest"
24+
azure "sigs.k8s.io/cluster-api-provider-azure/cloud"
25+
)
26+
27+
// Client wraps go-sdk
28+
type Client interface {
29+
Get(context.Context, string, string, string) (network.InboundNatRule, error)
30+
CreateOrUpdate(context.Context, string, string, string, network.InboundNatRule) error
31+
Delete(context.Context, string, string, string) error
32+
}
33+
34+
// AzureClient contains the Azure go-sdk Client
35+
type AzureClient struct {
36+
inboundnatrules network.InboundNatRulesClient
37+
}
38+
39+
var _ Client = &AzureClient{}
40+
41+
// NewClient creates a new inbound NAT rules client from subscription ID.
42+
func NewClient(subscriptionID string, authorizer autorest.Authorizer) *AzureClient {
43+
c := newInboundNatRulesClient(subscriptionID, authorizer)
44+
return &AzureClient{c}
45+
}
46+
47+
// newLoadbalancersClient creates a new inbound NAT rules client from subscription ID.
48+
func newInboundNatRulesClient(subscriptionID string, authorizer autorest.Authorizer) network.InboundNatRulesClient {
49+
inboundNatRulesClient := network.NewInboundNatRulesClient(subscriptionID)
50+
inboundNatRulesClient.Authorizer = authorizer
51+
inboundNatRulesClient.AddToUserAgent(azure.UserAgent)
52+
return inboundNatRulesClient
53+
}
54+
55+
// Get gets the specified inbound NAT rules.
56+
func (ac *AzureClient) Get(ctx context.Context, resourceGroupName, lbName, inboundNatRuleName string) (network.InboundNatRule, error) {
57+
return ac.inboundnatrules.Get(ctx, resourceGroupName, lbName, inboundNatRuleName, "")
58+
}
59+
60+
// CreateOrUpdate creates or updates a inbound NAT rules.
61+
func (ac *AzureClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, lbName string, inboundNatRuleName string, inboundNatRuleParameters network.InboundNatRule) error {
62+
future, err := ac.inboundnatrules.CreateOrUpdate(ctx, resourceGroupName, lbName, inboundNatRuleName, inboundNatRuleParameters)
63+
if err != nil {
64+
return err
65+
}
66+
err = future.WaitForCompletionRef(ctx, ac.inboundnatrules.Client)
67+
if err != nil {
68+
return err
69+
}
70+
_, err = future.Result(ac.inboundnatrules)
71+
return err
72+
}
73+
74+
// Delete deletes the specified inbound NAT rules.
75+
func (ac *AzureClient) Delete(ctx context.Context, resourceGroupName, lbName, inboundNatRuleName string) error {
76+
future, err := ac.inboundnatrules.Delete(ctx, resourceGroupName, lbName, inboundNatRuleName)
77+
if err != nil {
78+
return err
79+
}
80+
err = future.WaitForCompletionRef(ctx, ac.inboundnatrules.Client)
81+
if err != nil {
82+
return err
83+
}
84+
_, err = future.Result(ac.inboundnatrules)
85+
return err
86+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
// Run go generate to regenerate this mock.
18+
//go:generate ../../../../hack/tools/bin/mockgen -destination inboundnatrules_mock.go -package mock_inboundnatrules -source ../client.go Client
19+
//go:generate /usr/bin/env bash -c "cat ../../../../hack/boilerplate/boilerplate.generatego.txt inboundnatrules_mock.go > _inboundnatrules_mock.go && mv _inboundnatrules_mock.go inboundnatrules_mock.go"
20+
package mock_inboundnatrules //nolint

cloud/services/inboundnatrules/mock_inboundnatrules/inboundnatrules_mock.go

Lines changed: 94 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cloud/services/internalloadbalancers/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type AzureClient struct {
3838

3939
var _ Client = &AzureClient{}
4040

41-
// NewClient creates a new VM client from subscription ID.
41+
// NewClient creates a new load balancer client from subscription ID.
4242
func NewClient(subscriptionID string, authorizer autorest.Authorizer) *AzureClient {
4343
c := newLoadBalancersClient(subscriptionID, authorizer)
4444
return &AzureClient{c}

cloud/services/networkinterfaces/networkinterfaces.go

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package networkinterfaces
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
2324
"github.com/Azure/go-autorest/autorest/to"
@@ -35,7 +36,6 @@ type Spec struct {
3536
PublicLoadBalancerName string
3637
InternalLoadBalancerName string
3738
PublicIPName string
38-
NatRule int
3939
}
4040

4141
// Get provides information about a network interface.
@@ -63,7 +63,7 @@ func (s *Service) Reconcile(ctx context.Context, spec interface{}) error {
6363

6464
subnet, err := s.SubnetsClient.Get(ctx, s.Scope.Vnet().ResourceGroup, nicSpec.VnetName, nicSpec.SubnetName)
6565
if err != nil {
66-
return errors.Wrap(err, "failed to get subNets")
66+
return errors.Wrap(err, "failed to get subnets")
6767
}
6868

6969
nicConfig.Subnet = &network.Subnet{ID: subnet.ID}
@@ -75,7 +75,8 @@ func (s *Service) Reconcile(ctx context.Context, spec interface{}) error {
7575

7676
backendAddressPools := []network.BackendAddressPool{}
7777
if nicSpec.PublicLoadBalancerName != "" {
78-
lb, lberr := s.LoadBalancersClient.Get(ctx, s.Scope.ResourceGroup(), nicSpec.PublicLoadBalancerName)
78+
// only control planes have an attached public LB
79+
lb, lberr := s.PublicLoadBalancersClient.Get(ctx, s.Scope.ResourceGroup(), nicSpec.PublicLoadBalancerName)
7980
if lberr != nil {
8081
return errors.Wrap(lberr, "failed to get publicLB")
8182
}
@@ -85,14 +86,21 @@ func (s *Service) Reconcile(ctx context.Context, spec interface{}) error {
8586
ID: (*lb.BackendAddressPools)[0].ID,
8687
})
8788

89+
ruleName := s.MachineScope.Name()
90+
naterr := s.createInboundNatRule(ctx, lb, ruleName)
91+
if naterr != nil {
92+
return errors.Wrap(naterr, "failed to create NAT rule")
93+
}
94+
8895
nicConfig.LoadBalancerInboundNatRules = &[]network.InboundNatRule{
8996
{
90-
ID: (*lb.InboundNatRules)[nicSpec.NatRule].ID,
97+
ID: to.StringPtr(fmt.Sprintf("%s/inboundNatRules/%s", to.String(lb.ID), ruleName)),
9198
},
9299
}
93100
}
94101
if nicSpec.InternalLoadBalancerName != "" {
95-
internalLB, ilberr := s.LoadBalancersClient.Get(ctx, s.Scope.ResourceGroup(), nicSpec.InternalLoadBalancerName)
102+
// only control planes have an attached internal LB
103+
internalLB, ilberr := s.InternalLoadBalancersClient.Get(ctx, s.Scope.ResourceGroup(), nicSpec.InternalLoadBalancerName)
96104
if ilberr != nil {
97105
return errors.Wrap(ilberr, "failed to get internalLB")
98106
}
@@ -143,14 +151,59 @@ func (s *Service) Delete(ctx context.Context, spec interface{}) error {
143151
}
144152
klog.V(2).Infof("deleting nic %s", nicSpec.Name)
145153
err := s.Client.Delete(ctx, s.Scope.ResourceGroup(), nicSpec.Name)
146-
if err != nil && azure.ResourceNotFound(err) {
147-
// already deleted
148-
return nil
149-
}
150-
if err != nil {
154+
if err != nil && !azure.ResourceNotFound(err) {
151155
return errors.Wrapf(err, "failed to delete network interface %s in resource group %s", nicSpec.Name, s.Scope.ResourceGroup())
152156
}
153-
154-
klog.V(2).Infof("successfully deleted nic %s", nicSpec.Name)
157+
NATRuleName := s.MachineScope.Name()
158+
err = s.InboundNATRulesClient.Delete(ctx, s.Scope.ResourceGroup(), nicSpec.PublicLoadBalancerName, NATRuleName)
159+
if err != nil && !azure.ResourceNotFound(err) {
160+
return errors.Wrapf(err, "failed to delete inbound NAT rule %s in load balancer %s", NATRuleName, nicSpec.PublicLoadBalancerName)
161+
}
162+
klog.V(2).Infof("successfully deleted nic %s and NAT rule %s", nicSpec.Name, NATRuleName)
155163
return nil
156164
}
165+
166+
func (s *Service) createInboundNatRule(ctx context.Context, lb network.LoadBalancer, ruleName string) error {
167+
var sshFrontendPort int32 = 22
168+
ports := make(map[int32]struct{})
169+
if lb.LoadBalancerPropertiesFormat == nil || lb.InboundNatRules == nil {
170+
return errors.Errorf("Could not get existing inbound NAT rules from load balancer %s properties", to.String(lb.Name))
171+
}
172+
for _, v := range *lb.InboundNatRules {
173+
if to.String(v.Name) == ruleName {
174+
// Inbound NAT Rule already exists, nothing to do here.
175+
klog.Infof("NAT rule %s already exists", ruleName)
176+
return nil
177+
}
178+
ports[*v.InboundNatRulePropertiesFormat.FrontendPort] = struct{}{}
179+
}
180+
if _, ok := ports[22]; ok {
181+
var i int32
182+
found := false
183+
for i = 2201; i < 2220; i++ {
184+
if _, ok := ports[i]; !ok {
185+
sshFrontendPort = i
186+
found = true
187+
break
188+
}
189+
}
190+
if !found {
191+
return errors.Errorf("Failed to find available SSH Frontend port for NAT Rule in load balancer %s for AzureMachine %s", to.String(lb.Name), ruleName)
192+
}
193+
}
194+
rule := network.InboundNatRule{
195+
Name: to.StringPtr(ruleName),
196+
InboundNatRulePropertiesFormat: &network.InboundNatRulePropertiesFormat{
197+
BackendPort: to.Int32Ptr(22),
198+
EnableFloatingIP: to.BoolPtr(false),
199+
IdleTimeoutInMinutes: to.Int32Ptr(4),
200+
FrontendIPConfiguration: &network.SubResource{
201+
ID: (*lb.FrontendIPConfigurations)[0].ID,
202+
},
203+
Protocol: network.TransportProtocolTCP,
204+
FrontendPort: &sshFrontendPort,
205+
},
206+
}
207+
klog.V(3).Infof("Creating rule %s using port %d", ruleName, sshFrontendPort)
208+
return s.InboundNATRulesClient.CreateOrUpdate(ctx, s.Scope.ResourceGroup(), to.String(lb.Name), ruleName, rule)
209+
}

0 commit comments

Comments
 (0)