Skip to content

Commit 179c5c0

Browse files
committed
Add missing, desired routes during route table reconciliation
1 parent 67de5c2 commit 179c5c0

2 files changed

Lines changed: 342 additions & 51 deletions

File tree

pkg/cloud/services/network/routetables.go

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package network
1818

1919
import (
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 %v 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 %v 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

Comments
 (0)