sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/services/inboundnatrules/inboundnatrules.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 inboundnatrules 18 19 import ( 20 "context" 21 22 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v4" 23 "github.com/pkg/errors" 24 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 25 "sigs.k8s.io/cluster-api-provider-azure/azure" 26 "sigs.k8s.io/cluster-api-provider-azure/azure/services/async" 27 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 28 ) 29 30 const serviceName = "inboundnatrules" 31 32 // InboundNatScope defines the scope interface for an inbound NAT service. 33 type InboundNatScope interface { 34 azure.ClusterDescriber 35 azure.AsyncStatusUpdater 36 APIServerLBName() string 37 InboundNatSpecs() []azure.ResourceSpecGetter 38 } 39 40 // Service provides operations on Azure resources. 41 type Service struct { 42 Scope InboundNatScope 43 client 44 async.Reconciler 45 } 46 47 // New creates a new service. 48 func New(scope InboundNatScope) (*Service, error) { 49 client, err := newClient(scope, scope.DefaultedAzureCallTimeout()) 50 if err != nil { 51 return nil, err 52 } 53 return &Service{ 54 Scope: scope, 55 client: client, 56 Reconciler: async.New[armnetwork.InboundNatRulesClientCreateOrUpdateResponse, 57 armnetwork.InboundNatRulesClientDeleteResponse](scope, client, client), 58 }, nil 59 } 60 61 // Name returns the service name. 62 func (s *Service) Name() string { 63 return serviceName 64 } 65 66 // Reconcile idempotently creates or updates an inbound NAT rule. 67 func (s *Service) Reconcile(ctx context.Context) error { 68 ctx, log, done := tele.StartSpanWithLogger(ctx, "inboundnatrules.Service.Reconcile") 69 defer done() 70 71 ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout()) 72 defer cancel() 73 74 // Externally managed clusters might not have an LB 75 if s.Scope.APIServerLBName() == "" { 76 log.V(4).Info("Skipping InboundNatRule reconciliation as the cluster has no LB configured") 77 return nil 78 } 79 80 specs := s.Scope.InboundNatSpecs() 81 if len(specs) == 0 { 82 return nil 83 } 84 85 existingRules, err := s.client.List(ctx, s.Scope.ResourceGroup(), s.Scope.APIServerLBName()) 86 if err != nil { 87 result := errors.Wrapf(err, "failed to get existing NAT rules") 88 s.Scope.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, result) 89 return result 90 } 91 92 portsInUse := make(map[int32]struct{}) 93 for _, rule := range existingRules { 94 portsInUse[*rule.Properties.FrontendPort] = struct{}{} // Mark frontend port as in use 95 } 96 97 // We go through the list of InboundNatSpecs to reconcile each one, independently of the result of the previous one. 98 // If multiple errors occur, we return the most pressing one. 99 // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (i.e. error creating) -> operationNotDoneError (i.e. creating in progress) -> no error (i.e. created) 100 var result error 101 for _, spec := range specs { 102 // Find an available SSH port for the rule. 103 sshFrontendPort, err := getAvailableSSHFrontendPort(portsInUse) 104 if err != nil { 105 return errors.Wrapf(err, "failed to find available SSH Frontend port for NAT Rule %s in load balancer %s", spec.ResourceName(), spec.OwnerResourceName()) 106 } 107 natRule, ok := spec.(*InboundNatSpec) 108 if !ok { 109 result = errors.Errorf("%T is not of type InboundNatSpec", spec) 110 } 111 natRule.SSHFrontendPort = &sshFrontendPort 112 // Add the SSH frontend port to the list of ports in use 113 portsInUse[sshFrontendPort] = struct{}{} 114 if _, err := s.CreateOrUpdateResource(ctx, natRule, serviceName); err != nil { 115 if !azure.IsOperationNotDoneError(err) || result == nil { 116 result = err 117 } 118 } 119 } 120 121 s.Scope.UpdatePutStatus(infrav1.InboundNATRulesReadyCondition, serviceName, result) 122 123 return result 124 } 125 126 // Delete deletes the inbound NAT rule with the provided name. 127 func (s *Service) Delete(ctx context.Context) error { 128 ctx, _, done := tele.StartSpanWithLogger(ctx, "inboundnatrules.Service.Delete") 129 defer done() 130 131 ctx, cancel := context.WithTimeout(ctx, s.Scope.DefaultedAzureServiceReconcileTimeout()) 132 defer cancel() 133 134 specs := s.Scope.InboundNatSpecs() 135 if len(specs) == 0 { 136 return nil 137 } 138 139 // We go through the list of InboundNatSpecs to delete each one, independently of the result of the previous one. 140 // If multiple errors occur, we return the most pressing one. 141 // Order of precedence (highest -> lowest) is: error that is not an operationNotDoneError (i.e. error deleting) -> operationNotDoneError (i.e. deleting in progress) -> no error (i.e. deleted) 142 var result error 143 for _, natRule := range specs { 144 if err := s.DeleteResource(ctx, natRule, serviceName); err != nil { 145 if !azure.IsOperationNotDoneError(err) || result == nil { 146 result = err 147 } 148 } 149 } 150 151 s.Scope.UpdateDeleteStatus(infrav1.InboundNATRulesReadyCondition, serviceName, result) 152 return result 153 } 154 155 // IsManaged returns always returns true as CAPZ does not support BYO inbound NAT rules. 156 func (s *Service) IsManaged(ctx context.Context) (bool, error) { 157 return true, nil 158 }