Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix attaching of HTTPRoutes to Gateway Listeners #1275

Merged
merged 1 commit into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 34 additions & 22 deletions internal/mode/static/state/change_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ var _ = Describe("ChangeProcessor", func() {
ValidFilters: true,
},
},
Valid: true,
Valid: true,
Attachable: true,
Conditions: []conditions.Condition{
staticConds.NewRouteBackendRefRefBackendNotFound(
"spec.rules[0].backendRefs[0].name: Not found: \"service\"",
Expand All @@ -434,8 +435,9 @@ var _ = Describe("ChangeProcessor", func() {
Idx: 1,
},
},
Rules: []graph.Rule{{ValidMatches: true, ValidFilters: true}},
Valid: true,
Rules: []graph.Rule{{ValidMatches: true, ValidFilters: true}},
Valid: true,
Attachable: true,
}

// This is the base case expected graph. Tests will manipulate this to add or remove elements
Expand All @@ -449,16 +451,18 @@ var _ = Describe("ChangeProcessor", func() {
Source: gw1,
Listeners: map[string]*graph.Listener{
"listener-80-1": {
Source: gw1.Spec.Listeners[0],
Valid: true,
Source: gw1.Spec.Listeners[0],
Valid: true,
Attachable: true,
Routes: map[types.NamespacedName]*graph.Route{
{Namespace: "test", Name: "hr-1"}: expRouteHR1,
},
SupportedKinds: []v1.RouteGroupKind{{Kind: "HTTPRoute"}},
},
"listener-443-1": {
Source: gw1.Spec.Listeners[1],
Valid: true,
Source: gw1.Spec.Listeners[1],
Valid: true,
Attachable: true,
Routes: map[types.NamespacedName]*graph.Route{
{Namespace: "test", Name: "hr-1"}: expRouteHR1,
},
Expand Down Expand Up @@ -541,32 +545,40 @@ var _ = Describe("ChangeProcessor", func() {
It("returns updated graph", func() {
processor.CaptureUpsertChange(gc)

// no ref grant exists yet for gw1
expGraph.Gateway.Listeners["listener-443-1"] = &graph.Listener{
Source: gw1.Spec.Listeners[1],
Valid: false,
Routes: map[types.NamespacedName]*graph.Route{},
Conditions: staticConds.NewListenerRefNotPermitted(
"Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant",
),
SupportedKinds: []v1.RouteGroupKind{{Kind: "HTTPRoute"}},
// No ref grant exists yet for gw1
// so the listener is not valid, but still attachable
expGraph.Gateway.Listeners["listener-443-1"].Valid = false
expGraph.Gateway.Listeners["listener-443-1"].ResolvedSecret = nil
expGraph.Gateway.Listeners["listener-443-1"].Conditions = staticConds.NewListenerRefNotPermitted(
"Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant",
)

expAttachment80 := &graph.ParentRefAttachmentStatus{
AcceptedHostnames: map[string][]string{
"listener-80-1": {"foo.example.com"},
},
Attached: true,
}

expAttachment := &graph.ParentRefAttachmentStatus{
AcceptedHostnames: map[string][]string{},
FailedCondition: staticConds.NewRouteInvalidListener(),
Attached: false,
expAttachment443 := &graph.ParentRefAttachmentStatus{
AcceptedHostnames: map[string][]string{
"listener-443-1": {"foo.example.com"},
},
Attached: true,
}

expGraph.Gateway.Listeners["listener-80-1"].Routes[hr1Name].ParentRefs[1].Attachment = expAttachment
expGraph.Gateway.Listeners["listener-80-1"].Routes[hr1Name].ParentRefs[0].Attachment = expAttachment80
expGraph.Gateway.Listeners["listener-443-1"].Routes[hr1Name].ParentRefs[1].Attachment = expAttachment443

// no ref grant exists yet for hr1
expGraph.Routes[hr1Name].ParentRefs[1].Attachment = expAttachment
expGraph.Routes[hr1Name].Conditions = []conditions.Condition{
staticConds.NewRouteInvalidListener(),
staticConds.NewRouteBackendRefRefNotPermitted(
"Backend ref to Service service-ns/service not permitted by any ReferenceGrant",
),
}
expGraph.Routes[hr1Name].ParentRefs[0].Attachment = expAttachment80
expGraph.Routes[hr1Name].ParentRefs[1].Attachment = expAttachment443

expGraph.ReferencedSecrets = nil

Expand Down
115 changes: 65 additions & 50 deletions internal/mode/static/state/dataplane/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "sigs.k8s.io/gateway-api/apis/v1"

"github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph"
Expand Down Expand Up @@ -202,70 +203,80 @@ func (hpr *hostPathRules) upsertListener(l *graph.Listener) {
hpr.httpsListeners = append(hpr.httpsListeners, l)
}

for routeNsName, r := range l.Routes {
var hostnames []string
for _, p := range r.ParentRefs {
if val, exist := p.Attachment.AcceptedHostnames[string(l.Source.Name)]; exist {
hostnames = val
}
for _, r := range l.Routes {
if !r.Valid {
continue
}

for _, h := range hostnames {
if prevListener, exists := hpr.listenersForHost[h]; exists {
// override the previous listener if the new one has a more specific hostname
if listenerHostnameMoreSpecific(l.Source.Hostname, prevListener.Source.Hostname) {
hpr.listenersForHost[h] = l
}
} else {
hpr.listenersForHost[h] = l
}
hpr.upsertRoute(r, l)
}
}

if _, exist := hpr.rulesPerHost[h]; !exist {
hpr.rulesPerHost[h] = make(map[pathAndType]PathRule)
}
func (hpr *hostPathRules) upsertRoute(route *graph.Route, listener *graph.Listener) {
var hostnames []string
for _, p := range route.ParentRefs {
if val, exist := p.Attachment.AcceptedHostnames[string(listener.Source.Name)]; exist {
hostnames = val
}
}

for i, rule := range r.Source.Spec.Rules {
if !r.Rules[i].ValidMatches {
continue
for _, h := range hostnames {
if prevListener, exists := hpr.listenersForHost[h]; exists {
// override the previous listener if the new one has a more specific hostname
if listenerHostnameMoreSpecific(listener.Source.Hostname, prevListener.Source.Hostname) {
hpr.listenersForHost[h] = listener
}
} else {
hpr.listenersForHost[h] = listener
}

var filters HTTPFilters
if r.Rules[i].ValidFilters {
filters = createHTTPFilters(rule.Filters)
} else {
filters = HTTPFilters{
InvalidFilter: &InvalidHTTPFilter{},
}
if _, exist := hpr.rulesPerHost[h]; !exist {
hpr.rulesPerHost[h] = make(map[pathAndType]PathRule)
}
}

for i, rule := range route.Source.Spec.Rules {
if !route.Rules[i].ValidMatches {
continue
}

var filters HTTPFilters
if route.Rules[i].ValidFilters {
filters = createHTTPFilters(rule.Filters)
} else {
filters = HTTPFilters{
InvalidFilter: &InvalidHTTPFilter{},
}
}

for _, h := range hostnames {
for _, m := range rule.Matches {
path := getPath(m.Path)
for _, h := range hostnames {
for _, m := range rule.Matches {
path := getPath(m.Path)

key := pathAndType{
path: path,
pathType: *m.Path.Type,
}
key := pathAndType{
path: path,
pathType: *m.Path.Type,
}

rule, exist := hpr.rulesPerHost[h][key]
if !exist {
rule.Path = path
rule.PathType = convertPathType(*m.Path.Type)
}
rule, exist := hpr.rulesPerHost[h][key]
if !exist {
rule.Path = path
rule.PathType = convertPathType(*m.Path.Type)
}

// create iteration variable inside the loop to fix implicit memory aliasing
om := r.Source.ObjectMeta
// create iteration variable inside the loop to fix implicit memory aliasing
om := route.Source.ObjectMeta

rule.MatchRules = append(rule.MatchRules, MatchRule{
Source: &om,
BackendGroup: newBackendGroup(r.Rules[i].BackendRefs, routeNsName, i),
Filters: filters,
Match: convertMatch(m),
})
routeNsName := client.ObjectKeyFromObject(route.Source)

hpr.rulesPerHost[h][key] = rule
}
rule.MatchRules = append(rule.MatchRules, MatchRule{
Source: &om,
BackendGroup: newBackendGroup(route.Rules[i].BackendRefs, routeNsName, i),
Filters: filters,
Match: convertMatch(m),
})

hpr.rulesPerHost[h][key] = rule
}
}
}
Expand Down Expand Up @@ -371,6 +382,10 @@ func buildUpstreams(
}

for _, route := range l.Routes {
if !route.Valid {
continue
}

for _, rule := range route.Rules {
if !rule.ValidMatches || !rule.ValidFilters {
// don't generate upstreams for rules that have invalid matches or filters
Expand Down
Loading
Loading