@@ -18,6 +18,7 @@ package network
1818
1919import (
2020 "context"
21+ "fmt"
2122 "strings"
2223
2324 "github.com/aws/aws-sdk-go-v2/aws"
@@ -71,16 +72,13 @@ func (s *Service) reconcileRouteTables() error {
7172 s .scope .Debug ("Subnet is already associated with route table" , "subnet-id" , sn .GetResourceID (), "route-table-id" , aws .ToString (rt .RouteTableId ))
7273 // TODO(vincepri): check that everything is in order, e.g. routes match the subnet type.
7374
74- // For managed environments we need to reconcile the routes of our tables if there is a mistmatch.
75- // For example, a gateway can be deleted and our controller will re-create it, then we replace the route
76- // for the subnet to allow traffic to flow.
77- for _ , currentRoute := range rt .Routes {
78- for i := range routes {
79- // Routes destination cidr blocks must be unique within a routing table.
80- // If there is a mistmatch, we replace the routing association.
81- if err := s .fixMismatchedRouting (routes [i ], currentRoute , rt ); err != nil {
82- return err
83- }
75+ // For managed environments we reconcile every desired route. A route whose
76+ // destination is missing gets created (e.g. for bring-your-own VPC that didn't have it),
77+ // and a route whose destination matches but points at the wrong target gets
78+ // replaced (e.g. a gateway that was deleted and re-created).
79+ for _ , route := range routes {
80+ if err := s .reconcileRoute (route , rt ); err != nil {
81+ return err
8482 }
8583 }
8684
@@ -126,48 +124,51 @@ func (s *Service) reconcileRouteTables() error {
126124 return nil
127125}
128126
129- func (s * Service ) fixMismatchedRouting (specRoute * ec2.CreateRouteInput , currentRoute types.Route , rt types.RouteTable ) error {
130- var input * ec2.ReplaceRouteInput
131- if specRoute .DestinationCidrBlock != nil {
132- if (currentRoute .DestinationCidrBlock != nil &&
133- aws .ToString (currentRoute .DestinationCidrBlock ) == aws .ToString (specRoute .DestinationCidrBlock )) &&
134- ((currentRoute .GatewayId != nil && aws .ToString (currentRoute .GatewayId ) != aws .ToString (specRoute .GatewayId )) ||
135- (currentRoute .NatGatewayId != nil && aws .ToString (currentRoute .NatGatewayId ) != aws .ToString (specRoute .NatGatewayId ))) {
136- input = & ec2.ReplaceRouteInput {
137- RouteTableId : rt .RouteTableId ,
138- DestinationCidrBlock : specRoute .DestinationCidrBlock ,
139- GatewayId : specRoute .GatewayId ,
140- NatGatewayId : specRoute .NatGatewayId ,
141- }
127+ // reconcileRoute ensures a single desired route exists in the route table pointing
128+ // at the desired target.
129+ func (s * Service ) reconcileRoute (specRoute * ec2.CreateRouteInput , rt types.RouteTable ) error {
130+ var current * types.Route
131+ for i := range rt .Routes {
132+ route := & rt .Routes [i ]
133+ if (specRoute .DestinationCidrBlock != nil && aws .ToString (route .DestinationCidrBlock ) == aws .ToString (specRoute .DestinationCidrBlock )) ||
134+ (specRoute .DestinationIpv6CidrBlock != nil && aws .ToString (route .DestinationIpv6CidrBlock ) == aws .ToString (specRoute .DestinationIpv6CidrBlock )) {
135+ current = route
136+ break
142137 }
143138 }
144- if specRoute .DestinationIpv6CidrBlock != nil {
145- if (currentRoute .DestinationIpv6CidrBlock != nil &&
146- aws .ToString (currentRoute .DestinationIpv6CidrBlock ) == aws .ToString (specRoute .DestinationIpv6CidrBlock )) &&
147- ((currentRoute .GatewayId != nil && aws .ToString (currentRoute .GatewayId ) != aws .ToString (specRoute .GatewayId )) ||
148- (currentRoute .NatGatewayId != nil && aws .ToString (currentRoute .NatGatewayId ) != aws .ToString (specRoute .NatGatewayId )) ||
149- (currentRoute .EgressOnlyInternetGatewayId != nil && aws .ToString (currentRoute .EgressOnlyInternetGatewayId ) != aws .ToString (specRoute .EgressOnlyInternetGatewayId ))) {
150- input = & ec2.ReplaceRouteInput {
151- RouteTableId : rt .RouteTableId ,
152- DestinationIpv6CidrBlock : specRoute .DestinationIpv6CidrBlock ,
153- DestinationPrefixListId : specRoute .DestinationPrefixListId ,
154- GatewayId : specRoute .GatewayId ,
155- NatGatewayId : specRoute .NatGatewayId ,
156- EgressOnlyInternetGatewayId : specRoute .EgressOnlyInternetGatewayId ,
157- }
158- }
139+
140+ if current != nil &&
141+ aws .ToString (current .GatewayId ) == aws .ToString (specRoute .GatewayId ) &&
142+ aws .ToString (current .NatGatewayId ) == aws .ToString (specRoute .NatGatewayId ) &&
143+ aws .ToString (current .EgressOnlyInternetGatewayId ) == aws .ToString (specRoute .EgressOnlyInternetGatewayId ) &&
144+ aws .ToString (current .CarrierGatewayId ) == aws .ToString (specRoute .CarrierGatewayId ) {
145+ // The route already exists with the desired target, nothing to do
146+ return nil
159147 }
160- if input != nil {
161- if err := wait .WaitForWithRetryable (wait .NewBackoff (), func () (bool , error ) {
162- if _ , err := s .EC2Client .ReplaceRoute (context .TODO (), input ); err != nil {
163- return false , err
164- }
165- return true , nil
166- }); err != nil {
167- record .Warnf (s .scope .InfraCluster (), "FailedReplaceRoute" , "Failed to replace outdated route on managed RouteTable %q: %v" , aws .ToString (rt .RouteTableId ), err )
168- return errors .Wrapf (err , "failed to replace outdated route on route table %q" , aws .ToString (rt .RouteTableId ))
148+
149+ routeDesc := describeRoute (specRoute )
150+ if err := wait .WaitForWithRetryable (wait .NewBackoff (), func () (bool , error ) {
151+ if current == nil {
152+ specRoute .RouteTableId = rt .RouteTableId
153+ _ , err := s .EC2Client .CreateRoute (context .TODO (), specRoute )
154+ return err == nil , err
169155 }
170- }
156+ _ , err := s .EC2Client .ReplaceRoute (context .TODO (), & ec2.ReplaceRouteInput {
157+ RouteTableId : rt .RouteTableId ,
158+ DestinationCidrBlock : specRoute .DestinationCidrBlock ,
159+ DestinationIpv6CidrBlock : specRoute .DestinationIpv6CidrBlock ,
160+ DestinationPrefixListId : specRoute .DestinationPrefixListId ,
161+ GatewayId : specRoute .GatewayId ,
162+ NatGatewayId : specRoute .NatGatewayId ,
163+ EgressOnlyInternetGatewayId : specRoute .EgressOnlyInternetGatewayId ,
164+ CarrierGatewayId : specRoute .CarrierGatewayId ,
165+ })
166+ return err == nil , err
167+ }, awserrors .RouteTableNotFound , awserrors .NATGatewayNotFound , awserrors .GatewayNotFound ); err != nil {
168+ record .Warnf (s .scope .InfraCluster (), "FailedReconcileRoute" , "Failed to reconcile route %s on managed RouteTable %q: %v" , routeDesc , aws .ToString (rt .RouteTableId ), err )
169+ return errors .Wrapf (err , "failed to reconcile route %s on route table %q" , routeDesc , aws .ToString (rt .RouteTableId ))
170+ }
171+ record .Eventf (s .scope .InfraCluster (), "SuccessfulReconcileRoute" , "Reconciled route %s on RouteTable %q" , routeDesc , aws .ToString (rt .RouteTableId ))
171172 return nil
172173}
173174
@@ -285,14 +286,15 @@ func (s *Service) createRouteTableWithRoutes(routes []*ec2.CreateRouteInput, isP
285286 }
286287 return true , nil
287288 }, awserrors .RouteTableNotFound , awserrors .NATGatewayNotFound , awserrors .GatewayNotFound ); err != nil {
288- record .Warnf (s .scope .InfraCluster (), "FailedCreateRoute" , "Failed to create route %s for RouteTable %q: %v" , route , aws .ToString (out .RouteTable .RouteTableId ), err )
289+ routeDesc := describeRoute (route )
290+ record .Warnf (s .scope .InfraCluster (), "FailedCreateRoute" , "Failed to create route %s for RouteTable %q: %v" , routeDesc , aws .ToString (out .RouteTable .RouteTableId ), err )
289291 errDel := s .deleteRouteTable (* out .RouteTable )
290292 if errDel != nil {
291293 record .Warnf (s .scope .InfraCluster (), "FailedDeleteRouteTable" , "Failed to delete managed RouteTable %q: %v" , aws .ToString (out .RouteTable .RouteTableId ), errDel )
292294 }
293- return nil , errors .Wrapf (err , "failed to create route in route table %q: %v " , aws .ToString (out .RouteTable .RouteTableId ), route )
295+ return nil , errors .Wrapf (err , "failed to create route in route table %q: %s " , aws .ToString (out .RouteTable .RouteTableId ), routeDesc )
294296 }
295- record .Eventf (s .scope .InfraCluster (), "SuccessfulCreateRoute" , "Created route %s for RouteTable %q" , route , aws .ToString (out .RouteTable .RouteTableId ))
297+ record .Eventf (s .scope .InfraCluster (), "SuccessfulCreateRoute" , "Created route %s for RouteTable %q" , describeRoute ( route ) , aws .ToString (out .RouteTable .RouteTableId ))
296298 }
297299
298300 return & infrav1.RouteTable {
@@ -446,3 +448,24 @@ func (s *Service) getRoutesForSubnet(sn *infrav1.SubnetSpec) ([]*ec2.CreateRoute
446448 }
447449 return s .getRoutesToPrivateSubnet (sn )
448450}
451+
452+ // describeRoute renders a CreateRouteInput as a human-readable string for logs and events.
453+ func describeRoute (route * ec2.CreateRouteInput ) string {
454+ if route == nil {
455+ return "<nil>"
456+ }
457+ var parts []string
458+ addPart := func (name string , value * string ) {
459+ if v := aws .ToString (value ); v != "" {
460+ parts = append (parts , fmt .Sprintf ("%s=%s" , name , v ))
461+ }
462+ }
463+ addPart ("destinationCidrBlock" , route .DestinationCidrBlock )
464+ addPart ("destinationIpv6CidrBlock" , route .DestinationIpv6CidrBlock )
465+ addPart ("destinationPrefixListId" , route .DestinationPrefixListId )
466+ addPart ("gatewayId" , route .GatewayId )
467+ addPart ("natGatewayId" , route .NatGatewayId )
468+ addPart ("egressOnlyInternetGatewayId" , route .EgressOnlyInternetGatewayId )
469+ addPart ("carrierGatewayId" , route .CarrierGatewayId )
470+ return "{" + strings .Join (parts , ", " ) + "}"
471+ }
0 commit comments