diff --git a/internal/mode/static/nginx/config/maps_test.go b/internal/mode/static/nginx/config/maps_test.go index 4c64c6781..96a4dae9f 100644 --- a/internal/mode/static/nginx/config/maps_test.go +++ b/internal/mode/static/nginx/config/maps_test.go @@ -16,7 +16,7 @@ func TestExecuteMaps(t *testing.T) { { MatchRules: []dataplane.MatchRule{ { - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Add: []dataplane.HTTPHeader{ { @@ -28,7 +28,7 @@ func TestExecuteMaps(t *testing.T) { }, }, { - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Add: []dataplane.HTTPHeader{ { @@ -40,7 +40,7 @@ func TestExecuteMaps(t *testing.T) { }, }, { - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Set: []dataplane.HTTPHeader{ { @@ -100,7 +100,7 @@ func TestBuildAddHeaderMaps(t *testing.T) { { MatchRules: []dataplane.MatchRule{ { - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Add: []dataplane.HTTPHeader{ { @@ -126,7 +126,7 @@ func TestBuildAddHeaderMaps(t *testing.T) { }, }, { - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Set: []dataplane.HTTPHeader{ { diff --git a/internal/mode/static/nginx/config/servers.go b/internal/mode/static/nginx/config/servers.go index 63e99b30f..ecd719f1f 100644 --- a/internal/mode/static/nginx/config/servers.go +++ b/internal/mode/static/nginx/config/servers.go @@ -6,8 +6,6 @@ import ( "strings" gotemplate "text/template" - "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/nginxinc/nginx-kubernetes-gateway/internal/mode/static/nginx/config/http" "github.com/nginxinc/nginx-kubernetes-gateway/internal/mode/static/state/dataplane" ) @@ -89,11 +87,9 @@ func createLocations(pathRules []dataplane.PathRule, listenerPort int32) []http. extLocations := initializeExternalLocations(rule, pathsAndTypes) for matchRuleIdx, r := range rule.MatchRules { - m := r.GetMatch() - buildLocations := extLocations - if len(rule.MatchRules) != 1 || !isPathOnlyMatch(m) { - intLocation, match := initializeInternalLocation(rule, matchRuleIdx, m) + if len(rule.MatchRules) != 1 || !isPathOnlyMatch(r.Match) { + intLocation, match := initializeInternalLocation(rule, matchRuleIdx, r.Match) buildLocations = []http.Location{intLocation} matches = append(matches, match) } @@ -228,20 +224,20 @@ func initializeExternalLocations( func initializeInternalLocation( rule dataplane.PathRule, matchRuleIdx int, - match v1beta1.HTTPRouteMatch, + match dataplane.Match, ) (http.Location, httpMatch) { path := createPathForMatch(rule.Path, rule.PathType, matchRuleIdx) return createMatchLocation(path), createHTTPMatch(match, path) } -func createReturnValForRedirectFilter(filter *v1beta1.HTTPRequestRedirectFilter, listenerPort int32) *http.Return { +func createReturnValForRedirectFilter(filter *dataplane.HTTPRequestRedirectFilter, listenerPort int32) *http.Return { if filter == nil { return nil } hostname := "$host" if filter.Hostname != nil { - hostname = string(*filter.Hostname) + hostname = *filter.Hostname } code := http.StatusFound @@ -251,7 +247,7 @@ func createReturnValForRedirectFilter(filter *v1beta1.HTTPRequestRedirectFilter, port := listenerPort if filter.Port != nil { - port = int32(*filter.Port) + port = *filter.Port } hostnamePort := fmt.Sprintf("%s:%d", hostname, port) @@ -286,7 +282,7 @@ func createReturnValForRedirectFilter(filter *v1beta1.HTTPRequestRedirectFilter, // If the request satisfies the httpMatch, NGINX will redirect the request to the location RedirectPath. type httpMatch struct { // Method is the HTTPMethod of the HTTPRouteMatch. - Method v1beta1.HTTPMethod `json:"method,omitempty"` + Method string `json:"method,omitempty"` // RedirectPath is the path to redirect the request to if the request satisfies the match conditions. RedirectPath string `json:"redirectPath,omitempty"` // Headers is a list of HTTPHeaders name value pairs with the format "{name}:{value}". @@ -297,7 +293,7 @@ type httpMatch struct { Any bool `json:"any,omitempty"` } -func createHTTPMatch(match v1beta1.HTTPRouteMatch, redirectPath string) httpMatch { +func createHTTPMatch(match dataplane.Match, redirectPath string) httpMatch { hm := httpMatch{ RedirectPath: redirectPath, } @@ -316,14 +312,12 @@ func createHTTPMatch(match v1beta1.HTTPRouteMatch, redirectPath string) httpMatc headerNames := make(map[string]struct{}) for _, h := range match.Headers { - if *h.Type == v1beta1.HeaderMatchExact { - // duplicate header names are not permitted by the spec - // only configure the first entry for every header name (case-insensitive) - lowerName := strings.ToLower(string(h.Name)) - if _, ok := headerNames[lowerName]; !ok { - headers = append(headers, createHeaderKeyValString(h)) - headerNames[lowerName] = struct{}{} - } + // duplicate header names are not permitted by the spec + // only configure the first entry for every header name (case-insensitive) + lowerName := strings.ToLower(h.Name) + if _, ok := headerNames[lowerName]; !ok { + headers = append(headers, createHeaderKeyValString(h)) + headerNames[lowerName] = struct{}{} } } hm.Headers = headers @@ -333,9 +327,7 @@ func createHTTPMatch(match v1beta1.HTTPRouteMatch, redirectPath string) httpMatc params := make([]string, 0, len(match.QueryParams)) for _, p := range match.QueryParams { - if *p.Type == v1beta1.QueryParamMatchExact { - params = append(params, createQueryParamKeyValString(p)) - } + params = append(params, createQueryParamKeyValString(p)) } hm.QueryParams = params } @@ -345,7 +337,7 @@ func createHTTPMatch(match v1beta1.HTTPRouteMatch, redirectPath string) httpMatc // The name and values are delimited by "=". A name and value can always be recovered using strings.SplitN(arg,"=", 2). // Query Parameters are case-sensitive so case is preserved. -func createQueryParamKeyValString(p v1beta1.HTTPQueryParamMatch) string { +func createQueryParamKeyValString(p dataplane.HTTPQueryParamMatch) string { return string(p.Name) + "=" + p.Value } @@ -354,12 +346,12 @@ func createQueryParamKeyValString(p v1beta1.HTTPQueryParamMatch) string { // Ex. foo:bar == FOO:bar, but foo:bar != foo:BAR, // We preserve the case of the name here because NGINX allows us to look up the header names in a case-insensitive // manner. -func createHeaderKeyValString(h v1beta1.HTTPHeaderMatch) string { +func createHeaderKeyValString(h dataplane.HTTPHeaderMatch) string { return string(h.Name) + HeaderMatchSeparator + h.Value } -func isPathOnlyMatch(match v1beta1.HTTPRouteMatch) bool { - return match.Method == nil && match.Headers == nil && match.QueryParams == nil +func isPathOnlyMatch(match dataplane.Match) bool { + return match.Method == nil && len(match.Headers) == 0 && len(match.QueryParams) == 0 } func createProxyPass(backendGroup dataplane.BackendGroup) string { diff --git a/internal/mode/static/nginx/config/servers_test.go b/internal/mode/static/nginx/config/servers_test.go index d524abbb9..7c77baea5 100644 --- a/internal/mode/static/nginx/config/servers_test.go +++ b/internal/mode/static/nginx/config/servers_test.go @@ -6,11 +6,8 @@ import ( "strings" "testing" - "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/nginxinc/nginx-kubernetes-gateway/internal/framework/helpers" "github.com/nginxinc/nginx-kubernetes-gateway/internal/mode/static/nginx/config/http" @@ -168,218 +165,7 @@ func TestCreateServers(t *testing.T) { sslKeyPairID = "test-keypair" ) - hr := &v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "route1", - }, - Spec: v1beta1.HTTPRouteSpec{ - Hostnames: []v1beta1.Hostname{ - "cafe.example.com", - }, - Rules: []v1beta1.HTTPRouteRule{ - { - // matches with path and methods - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodPost), - }, - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodPatch), - }, - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer( - "/", // should generate an "any" httpmatch since other matches exists for / - ), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - }, - { - // A match with all possible fields set - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/test"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodGet), - Headers: []v1beta1.HTTPHeaderMatch{ - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), - Name: "Version", - Value: "V1", - }, - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), - Name: "test", - Value: "foo", - }, - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), - Name: "my-header", - Value: "my-value", - }, - }, - QueryParams: []v1beta1.HTTPQueryParamMatch{ - { - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchExact), - Name: "GrEat", // query names and values should not be normalized to lowercase - Value: "EXAMPLE", - }, - { - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchExact), - Name: "test", - Value: "foo=bar", - }, - }, - }, - }, - }, - { - // A match with just path - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-only"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - }, - { - // A match with a redirect with implicit port - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/redirect-implicit-port"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - // redirect is set in the corresponding state.MatchRule - }, - { - // A match with a redirect with explicit port - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/redirect-explicit-port"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - // redirect is set in the corresponding state.MatchRule - }, - { - // A match with a redirect and header matches - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/redirect-with-headers"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - Headers: []v1beta1.HTTPHeaderMatch{ - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), - Name: "redirect", - Value: "this", - }, - }, - }, - }, - }, - { - // A match with an invalid filter - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetPointer("/invalid-filter"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - }, - { - // A match with an invalid filter and headers - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetPointer("/invalid-filter-with-headers"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - Headers: []v1beta1.HTTPHeaderMatch{ - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), - Name: "filter", - Value: "this", - }, - }, - }, - }, - }, - { - // A match using type Exact - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetPointer("/exact"), - Type: helpers.GetPointer(v1beta1.PathMatchExact), - }, - }, - }, - }, - { - // A match using type Exact with method - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetPointer("/test"), - Type: helpers.GetPointer(v1beta1.PathMatchExact), - }, - Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodGet), - }, - }, - }, - { - // A match with requestHeaderModifier filter set - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/proxy-set-headers"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - Filters: []v1beta1.HTTPRouteFilter{ - { - Type: "RequestHeaderModifier", - RequestHeaderModifier: &v1beta1.HTTPHeaderFilter{ - Add: []v1beta1.HTTPHeader{ - { - Name: "my-header", - Value: "some-value-123", - }, - }, - }, - }, - }, - }, - }, - }, - } - - hrNsName := types.NamespacedName{Namespace: hr.Namespace, Name: hr.Name} + hrNsName := types.NamespacedName{Namespace: "test", Name: "route1"} fooGroup := dataplane.BackendGroup{ Source: hrNsName, @@ -436,22 +222,21 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, + Match: dataplane.Match{ + Method: helpers.GetPointer("POST"), + }, BackendGroup: fooGroup, - Source: hr, }, { - MatchIdx: 1, - RuleIdx: 0, + Match: dataplane.Match{ + Method: helpers.GetPointer("PATCH"), + }, BackendGroup: fooGroup, - Source: hr, }, { - MatchIdx: 2, - RuleIdx: 0, + // should generate an "any" httpmatch since other matches exists for / + Match: dataplane.Match{}, BackendGroup: fooGroup, - Source: hr, }, }, }, @@ -460,10 +245,36 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, + // A match with all possible fields set + Match: dataplane.Match{ + Method: helpers.GetPointer("GET"), + Headers: []dataplane.HTTPHeaderMatch{ + { + Name: "Version", + Value: "V1", + }, + { + Name: "test", + Value: "foo", + }, + { + Name: "my-header", + Value: "my-value", + }, + }, + QueryParams: []dataplane.HTTPQueryParamMatch{ + { + // query names and values should not be normalized to lowercase + Name: "GrEat", + Value: "EXAMPLE", + }, + { + Name: "test", + Value: "foo=bar", + }, + }, + }, BackendGroup: barGroup, - Source: hr, }, }, }, @@ -472,10 +283,8 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 2, + Match: dataplane.Match{}, BackendGroup: bazGroup, - Source: hr, }, }, }, @@ -484,12 +293,10 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 3, - Source: hr, - Filters: dataplane.Filters{ - RequestRedirect: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: (*v1beta1.PreciseHostname)(helpers.GetStringPointer("foo.example.com")), + Match: dataplane.Match{}, + Filters: dataplane.HTTPFilters{ + RequestRedirect: &dataplane.HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("foo.example.com"), }, }, BackendGroup: filterGroup1, @@ -501,13 +308,11 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 4, - Source: hr, - Filters: dataplane.Filters{ - RequestRedirect: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: (*v1beta1.PreciseHostname)(helpers.GetStringPointer("bar.example.com")), - Port: (*v1beta1.PortNumber)(helpers.GetInt32Pointer(8080)), + Match: dataplane.Match{}, + Filters: dataplane.HTTPFilters{ + RequestRedirect: &dataplane.HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("bar.example.com"), + Port: helpers.GetPointer[int32](8080), }, }, BackendGroup: filterGroup2, @@ -519,13 +324,18 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 5, - Source: hr, - Filters: dataplane.Filters{ - RequestRedirect: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), - Port: helpers.GetPointer(v1beta1.PortNumber(8080)), + Match: dataplane.Match{ + Headers: []dataplane.HTTPHeaderMatch{ + { + Name: "redirect", + Value: "this", + }, + }, + }, + Filters: dataplane.HTTPFilters{ + RequestRedirect: &dataplane.HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("foo.example.com"), + Port: helpers.GetPointer[int32](8080), }, }, BackendGroup: filterGroup1, @@ -537,11 +347,9 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 6, - Source: hr, - Filters: dataplane.Filters{ - InvalidFilter: &dataplane.InvalidFilter{}, + Match: dataplane.Match{}, + Filters: dataplane.HTTPFilters{ + InvalidFilter: &dataplane.InvalidHTTPFilter{}, }, BackendGroup: invalidFilterGroup, }, @@ -552,11 +360,16 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 7, - Source: hr, - Filters: dataplane.Filters{ - InvalidFilter: &dataplane.InvalidFilter{}, + Match: dataplane.Match{ + Headers: []dataplane.HTTPHeaderMatch{ + { + Name: "filter", + Value: "this", + }, + }, + }, + Filters: dataplane.HTTPFilters{ + InvalidFilter: &dataplane.InvalidHTTPFilter{}, }, BackendGroup: invalidFilterGroup, }, @@ -567,9 +380,7 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 8, - Source: hr, + Match: dataplane.Match{}, BackendGroup: fooGroup, }, }, @@ -579,9 +390,9 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 9, - Source: hr, + Match: dataplane.Match{ + Method: helpers.GetPointer("GET"), + }, BackendGroup: fooGroup, }, }, @@ -591,11 +402,9 @@ func TestCreateServers(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 10, - Source: hr, + Match: dataplane.Match{}, BackendGroup: fooGroup, - Filters: dataplane.Filters{ + Filters: dataplane.HTTPFilters{ RequestHeaderModifiers: &dataplane.HTTPHeaderFilter{ Add: []dataplane.HTTPHeader{ { @@ -644,13 +453,13 @@ func TestCreateServers(t *testing.T) { } slashMatches := []httpMatch{ - {Method: v1beta1.HTTPMethodPost, RedirectPath: "/_prefix_route0"}, - {Method: v1beta1.HTTPMethodPatch, RedirectPath: "/_prefix_route1"}, + {Method: "POST", RedirectPath: "/_prefix_route0"}, + {Method: "PATCH", RedirectPath: "/_prefix_route1"}, {Any: true, RedirectPath: "/_prefix_route2"}, } testMatches := []httpMatch{ { - Method: v1beta1.HTTPMethodGet, + Method: "GET", Headers: []string{"Version:V1", "test:foo", "my-header:my-value"}, QueryParams: []string{"GrEat=EXAMPLE", "test=foo=bar"}, RedirectPath: "/test_prefix_route0", @@ -658,7 +467,7 @@ func TestCreateServers(t *testing.T) { } exactMatches := []httpMatch{ { - Method: v1beta1.HTTPMethodGet, + Method: "GET", RedirectPath: "/test_exact_route0", }, } @@ -859,74 +668,6 @@ func TestCreateServers(t *testing.T) { } func TestCreateServersConflicts(t *testing.T) { - type pathAndType struct { - path string - pathType v1beta1.PathMatchType - } - - createHR := func(pathsAndTypes []pathAndType) *v1beta1.HTTPRoute { - hr := &v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "route", - }, - Spec: v1beta1.HTTPRouteSpec{ - Hostnames: []v1beta1.Hostname{ - "cafe.example.com", - }, - Rules: []v1beta1.HTTPRouteRule{}, - }, - } - for _, pt := range pathsAndTypes { - match := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetPointer(pt.path), - Type: helpers.GetPointer(pt.pathType), - }, - } - hr.Spec.Rules = append(hr.Spec.Rules, v1beta1.HTTPRouteRule{ - Matches: []v1beta1.HTTPRouteMatch{match}, - }) - } - - return hr - } - - hr1 := createHR([]pathAndType{ - { - path: "/coffee", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee", - pathType: v1beta1.PathMatchExact, - }, - }) - hr2 := createHR([]pathAndType{ - { - path: "/coffee", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee/", - pathType: v1beta1.PathMatchPathPrefix, - }, - }) - hr3 := createHR([]pathAndType{ - { - path: "/coffee", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee/", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee", - pathType: v1beta1.PathMatchExact, - }, - }) - fooGroup := dataplane.BackendGroup{ Source: types.NamespacedName{Namespace: "test", Name: "route"}, RuleIdx: 0, @@ -974,9 +715,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, - Source: hr1, + Match: dataplane.Match{}, BackendGroup: fooGroup, }, }, @@ -986,9 +725,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, - Source: hr1, + Match: dataplane.Match{}, BackendGroup: barGroup, }, }, @@ -1014,9 +751,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, - Source: hr2, + Match: dataplane.Match{}, BackendGroup: fooGroup, }, }, @@ -1026,9 +761,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, - Source: hr2, + Match: dataplane.Match{}, BackendGroup: barGroup, }, }, @@ -1054,9 +787,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, - Source: hr3, + Match: dataplane.Match{}, BackendGroup: fooGroup, }, }, @@ -1066,9 +797,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypePrefix, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, - Source: hr3, + Match: dataplane.Match{}, BackendGroup: barGroup, }, }, @@ -1078,22 +807,7 @@ func TestCreateServersConflicts(t *testing.T) { PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { - MatchIdx: 0, - RuleIdx: 2, - Source: createHR([]pathAndType{ - { - path: "/coffee", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee/", - pathType: v1beta1.PathMatchPathPrefix, - }, - { - path: "/coffee", - pathType: v1beta1.PathMatchExact, - }, - }), + Match: dataplane.Match{}, BackendGroup: bazGroup, }, }, @@ -1149,53 +863,6 @@ func TestCreateServersConflicts(t *testing.T) { func TestCreateLocationsRootPath(t *testing.T) { g := NewGomegaWithT(t) - createRoute := func(rootPath bool) *v1beta1.HTTPRoute { - route := &v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "route1", - }, - Spec: v1beta1.HTTPRouteSpec{ - Hostnames: []v1beta1.Hostname{ - "cafe.example.com", - }, - Rules: []v1beta1.HTTPRouteRule{ - { - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-1"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-2"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }, - }, - }, - }, - }, - } - - if rootPath { - route.Spec.Rules[0].Matches = append(route.Spec.Rules[0].Matches, v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/"), - Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), - }, - }) - } - - return route - } - - hrWithRootPathRule := createRoute(true) - - hrWithoutRootPathRule := createRoute(false) - hrNsName := types.NamespacedName{Namespace: "test", Name: "route1"} fooGroup := dataplane.BackendGroup{ @@ -1210,16 +877,14 @@ func TestCreateLocationsRootPath(t *testing.T) { }, } - getPathRules := func(source *v1beta1.HTTPRoute, rootPath bool) []dataplane.PathRule { + getPathRules := func(rootPath bool) []dataplane.PathRule { rules := []dataplane.PathRule{ { Path: "/path-1", MatchRules: []dataplane.MatchRule{ { - Source: source, + Match: dataplane.Match{}, BackendGroup: fooGroup, - MatchIdx: 0, - RuleIdx: 0, }, }, }, @@ -1227,10 +892,8 @@ func TestCreateLocationsRootPath(t *testing.T) { Path: "/path-2", MatchRules: []dataplane.MatchRule{ { - Source: source, + Match: dataplane.Match{}, BackendGroup: fooGroup, - MatchIdx: 1, - RuleIdx: 0, }, }, }, @@ -1241,10 +904,8 @@ func TestCreateLocationsRootPath(t *testing.T) { Path: "/", MatchRules: []dataplane.MatchRule{ { - Source: source, + Match: dataplane.Match{}, BackendGroup: fooGroup, - MatchIdx: 2, - RuleIdx: 0, }, }, }) @@ -1260,7 +921,7 @@ func TestCreateLocationsRootPath(t *testing.T) { }{ { name: "path rules with no root path should generate a default 404 root location", - pathRules: getPathRules(hrWithoutRootPathRule, false), + pathRules: getPathRules(false /* rootPath */), expLocations: []http.Location{ { Path: "/path-1", @@ -1280,7 +941,7 @@ func TestCreateLocationsRootPath(t *testing.T) { }, { name: "path rules with a root path should not generate a default 404 root path", - pathRules: getPathRules(hrWithRootPathRule, true), + pathRules: getPathRules(true /* rootPath */), expLocations: []http.Location{ { Path: "/path-1", @@ -1322,7 +983,7 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { const listenerPortHTTPS = 443 tests := []struct { - filter *v1beta1.HTTPRequestRedirectFilter + filter *dataplane.HTTPRequestRedirectFilter expected *http.Return msg string listenerPort int32 @@ -1334,7 +995,7 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "filter is nil", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{}, + filter: &dataplane.HTTPRequestRedirectFilter{}, listenerPort: listenerPortCustom, expected: &http.Return{ Code: http.StatusFound, @@ -1343,10 +1004,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "all fields are empty", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("https"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), - Port: (*v1beta1.PortNumber)(helpers.GetInt32Pointer(2022)), + Hostname: helpers.GetPointer("foo.example.com"), + Port: helpers.GetPointer[int32](2022), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortCustom, @@ -1357,9 +1018,9 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "all fields are set", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("https"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), + Hostname: helpers.GetPointer("foo.example.com"), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortCustom, @@ -1370,8 +1031,8 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "listenerPort is custom, scheme is set, no port", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), + filter: &dataplane.HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("foo.example.com"), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortHTTPS, @@ -1382,9 +1043,9 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "no scheme, listenerPort https, no port is set", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("https"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), + Hostname: helpers.GetPointer("foo.example.com"), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortHTTPS, @@ -1395,9 +1056,9 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "scheme is https, listenerPort https, no port is set", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("http"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), + Hostname: helpers.GetPointer("foo.example.com"), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortHTTP, @@ -1408,10 +1069,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "scheme is http, listenerPort http, no port is set", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("http"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), - Port: (*v1beta1.PortNumber)(helpers.GetInt32Pointer(80)), + Hostname: helpers.GetPointer("foo.example.com"), + Port: helpers.GetPointer[int32](80), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortCustom, @@ -1422,10 +1083,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { msg: "scheme is http, port http", }, { - filter: &v1beta1.HTTPRequestRedirectFilter{ + filter: &dataplane.HTTPRequestRedirectFilter{ Scheme: helpers.GetPointer("https"), - Hostname: helpers.GetPointer(v1beta1.PreciseHostname("foo.example.com")), - Port: (*v1beta1.PortNumber)(helpers.GetInt32Pointer(443)), + Hostname: helpers.GetPointer("foo.example.com"), + Port: helpers.GetPointer[int32](443), StatusCode: helpers.GetPointer(301), }, listenerPort: listenerPortCustom, @@ -1438,70 +1099,52 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } for _, test := range tests { - result := createReturnValForRedirectFilter(test.filter, test.listenerPort) - if diff := cmp.Diff(test.expected, result); diff != "" { - t.Errorf("createReturnValForRedirectFilter() mismatch %q (-want +got):\n%s", test.msg, diff) - } + t.Run(test.msg, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := createReturnValForRedirectFilter(test.filter, test.listenerPort) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } func TestCreateHTTPMatch(t *testing.T) { testPath := "/internal_loc" - testPathMatch := v1beta1.HTTPPathMatch{Value: helpers.GetStringPointer("/")} - testMethodMatch := helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodPut) - testHeaderMatches := []v1beta1.HTTPHeaderMatch{ + testMethodMatch := helpers.GetPointer("PUT") + testHeaderMatches := []dataplane.HTTPHeaderMatch{ { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), Name: "header-1", Value: "val-1", }, { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), Name: "header-2", Value: "val-2", }, { - // regex type is not supported. This should not be added to the httpMatch headers. - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchRegularExpression), - Name: "ignore-this-header", - Value: "val", - }, - { - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), Name: "header-3", Value: "val-3", }, } - testDuplicateHeaders := make([]v1beta1.HTTPHeaderMatch, 0, 5) - duplicateHeaderMatch := v1beta1.HTTPHeaderMatch{ - Type: helpers.GetHeaderMatchTypePointer(v1beta1.HeaderMatchExact), + testDuplicateHeaders := make([]dataplane.HTTPHeaderMatch, 0, 4) + duplicateHeaderMatch := dataplane.HTTPHeaderMatch{ Name: "HEADER-2", // header names are case-insensitive Value: "val-2", } testDuplicateHeaders = append(testDuplicateHeaders, testHeaderMatches...) testDuplicateHeaders = append(testDuplicateHeaders, duplicateHeaderMatch) - testQueryParamMatches := []v1beta1.HTTPQueryParamMatch{ + testQueryParamMatches := []dataplane.HTTPQueryParamMatch{ { - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchExact), Name: "arg1", Value: "val1", }, { - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchExact), Name: "arg2", Value: "val2=another-val", }, { - // regex type is not supported. This should not be added to the httpMatch args - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchRegularExpression), - Name: "ignore-this-arg", - Value: "val", - }, - { - Type: helpers.GetQueryParamMatchTypePointer(v1beta1.QueryParamMatchExact), Name: "arg3", Value: "==val3", }, @@ -1511,14 +1154,12 @@ func TestCreateHTTPMatch(t *testing.T) { expectedArgs := []string{"arg1=val1", "arg2=val2=another-val", "arg3===val3"} tests := []struct { - match v1beta1.HTTPRouteMatch + match dataplane.Match msg string expected httpMatch }{ { - match: v1beta1.HTTPRouteMatch{ - Path: &testPathMatch, - }, + match: dataplane.Match{}, expected: httpMatch{ Any: true, RedirectPath: testPath, @@ -1526,9 +1167,8 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "path only match", }, { - match: v1beta1.HTTPRouteMatch{ - Path: &testPathMatch, // A path match with a method should not set the Any field to true - Method: testMethodMatch, + match: dataplane.Match{ + Method: testMethodMatch, // A path match with a method should not set the Any field to true }, expected: httpMatch{ Method: "PUT", @@ -1537,7 +1177,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "method only match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ Headers: testHeaderMatches, }, expected: httpMatch{ @@ -1547,7 +1187,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "headers only match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ QueryParams: testQueryParamMatches, }, expected: httpMatch{ @@ -1557,7 +1197,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "query params only match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ Method: testMethodMatch, QueryParams: testQueryParamMatches, }, @@ -1569,7 +1209,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "method and query params match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ Method: testMethodMatch, Headers: testHeaderMatches, }, @@ -1581,7 +1221,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "method and headers match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ QueryParams: testQueryParamMatches, Headers: testHeaderMatches, }, @@ -1593,7 +1233,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "query params and headers match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ Headers: testHeaderMatches, QueryParams: testQueryParamMatches, Method: testMethodMatch, @@ -1607,7 +1247,7 @@ func TestCreateHTTPMatch(t *testing.T) { msg: "method, headers, and query params match", }, { - match: v1beta1.HTTPRouteMatch{ + match: dataplane.Match{ Headers: testDuplicateHeaders, }, expected: httpMatch{ @@ -1618,85 +1258,77 @@ func TestCreateHTTPMatch(t *testing.T) { }, } for _, tc := range tests { - result := createHTTPMatch(tc.match, testPath) - if diff := helpers.Diff(result, tc.expected); diff != "" { - t.Errorf("createHTTPMatch() returned incorrect httpMatch for test case: %q, diff: %+v", tc.msg, diff) - } + t.Run(tc.msg, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := createHTTPMatch(tc.match, testPath) + g.Expect(helpers.Diff(result, tc.expected)).To(BeEmpty()) + }) } } func TestCreateQueryParamKeyValString(t *testing.T) { + g := NewGomegaWithT(t) + expected := "key=value" result := createQueryParamKeyValString( - v1beta1.HTTPQueryParamMatch{ + dataplane.HTTPQueryParamMatch{ Name: "key", Value: "value", }, ) - if result != expected { - t.Errorf("createQueryParamKeyValString() returned %q but expected %q", result, expected) - } + + g.Expect(result).To(Equal(expected)) expected = "KeY=vaLUe==" result = createQueryParamKeyValString( - v1beta1.HTTPQueryParamMatch{ + dataplane.HTTPQueryParamMatch{ Name: "KeY", Value: "vaLUe==", }, ) - if result != expected { - t.Errorf("createQueryParamKeyValString() returned %q but expected %q", result, expected) - } + + g.Expect(result).To(Equal(expected)) } func TestCreateHeaderKeyValString(t *testing.T) { + g := NewGomegaWithT(t) + expected := "kEy:vALUe" result := createHeaderKeyValString( - v1beta1.HTTPHeaderMatch{ + dataplane.HTTPHeaderMatch{ Name: "kEy", Value: "vALUe", }, ) - if result != expected { - t.Errorf("createHeaderKeyValString() returned %q but expected %q", result, expected) - } + g.Expect(result).To(Equal(expected)) } func TestIsPathOnlyMatch(t *testing.T) { tests := []struct { - match v1beta1.HTTPRouteMatch + match dataplane.Match msg string expected bool }{ { - match: v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - }, + match: dataplane.Match{}, expected: true, msg: "path only match", }, { - match: v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Method: helpers.GetHTTPMethodPointer(v1beta1.HTTPMethodGet), + match: dataplane.Match{ + Method: helpers.GetPointer("GET"), }, expected: false, msg: "method defined in match", }, { - match: v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Headers: []v1beta1.HTTPHeaderMatch{ + match: dataplane.Match{ + Headers: []dataplane.HTTPHeaderMatch{ { Name: "header", Value: "val", @@ -1707,11 +1339,8 @@ func TestIsPathOnlyMatch(t *testing.T) { msg: "headers defined in match", }, { - match: v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - QueryParams: []v1beta1.HTTPQueryParamMatch{ + match: dataplane.Match{ + QueryParams: []dataplane.HTTPQueryParamMatch{ { Name: "arg", Value: "val", @@ -1724,11 +1353,12 @@ func TestIsPathOnlyMatch(t *testing.T) { } for _, tc := range tests { - result := isPathOnlyMatch(tc.match) + t.Run(tc.msg, func(t *testing.T) { + g := NewGomegaWithT(t) - if result != tc.expected { - t.Errorf("isPathOnlyMatch() returned %t but expected %t for test case %q", result, tc.expected, tc.msg) - } + result := isPathOnlyMatch(tc.match) + g.Expect(result).To(Equal(tc.expected)) + }) } } diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index 0205db641..6cf887537 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -13,149 +13,7 @@ import ( "github.com/nginxinc/nginx-kubernetes-gateway/internal/mode/static/state/resolver" ) -type PathType string - -const ( - wildcardHostname = "~^" - PathTypePrefix PathType = "prefix" - PathTypeExact PathType = "exact" -) - -// Configuration is an intermediate representation of dataplane configuration. -type Configuration struct { - // SSLKeyPairs holds all unique SSLKeyPairs. - SSLKeyPairs map[SSLKeyPairID]SSLKeyPair - // HTTPServers holds all HTTPServers. - HTTPServers []VirtualServer - // SSLServers holds all SSLServers. - SSLServers []VirtualServer - // Upstreams holds all unique Upstreams. - Upstreams []Upstream - // BackendGroups holds all unique BackendGroups. - BackendGroups []BackendGroup -} - -// SSLKeyPairID is a unique identifier for a SSLKeyPair. -// The ID is safe to use as a file name. -type SSLKeyPairID string - -// SSLKeyPair is an SSL private/public key pair. -type SSLKeyPair struct { - Cert, Key []byte -} - -// VirtualServer is a virtual server. -type VirtualServer struct { - // SSL holds the SSL configuration for the server. - SSL *SSL - // Hostname is the hostname of the server. - Hostname string - // PathRules is a collection of routing rules. - PathRules []PathRule - // IsDefault indicates whether the server is the default server. - IsDefault bool - // Port is the port of the server. - Port int32 -} - -// Upstream is a pool of endpoints to be load balanced. -type Upstream struct { - // Name is the name of the Upstream. Will be unique for each service/port combination. - Name string - // ErrorMsg contains the error message if the Upstream is invalid. - ErrorMsg string - // Endpoints are the endpoints of the Upstream. - Endpoints []resolver.Endpoint -} - -// SSL is the SSL configuration for a server. -type SSL struct { - KeyPairID SSLKeyPairID -} - -// PathRule represents routing rules that share a common path. -type PathRule struct { - // Path is a path. For example, '/hello'. - Path string - // PathType is simplified path type. For example, prefix or exact. - PathType PathType - // MatchRules holds routing rules. - MatchRules []MatchRule -} - -type HTTPHeaderFilter struct { - Set []HTTPHeader - Add []HTTPHeader - Remove []string -} - -type HTTPHeader struct { - Name string - Value string -} - -// InvalidFilter is a special filter for handling the case when configured filters are invalid. -type InvalidFilter struct{} - -// Filters hold the filters for a MatchRule. -type Filters struct { - InvalidFilter *InvalidFilter - RequestRedirect *v1beta1.HTTPRequestRedirectFilter - RequestHeaderModifiers *HTTPHeaderFilter -} - -// MatchRule represents a routing rule. It corresponds directly to a Match in the HTTPRoute resource. -// An HTTPRoute is guaranteed to have at least one rule with one match. -// If no rule or match is specified by the user, the default rule {{path:{ type: "PathPrefix", value: "/"}}} -// is set by the schema. -type MatchRule struct { - // Filters holds the filters for the MatchRule. - Filters Filters - // Source is the corresponding HTTPRoute resource. - Source *v1beta1.HTTPRoute - // BackendGroup is the group of Backends that the rule routes to. - BackendGroup BackendGroup - // MatchIdx is the index of the rule in the Rule.Matches. - MatchIdx int - // RuleIdx is the index of the corresponding rule in the HTTPRoute. - RuleIdx int -} - -// BackendGroup represents a group of Backends for a routing rule in an HTTPRoute. -type BackendGroup struct { - // Source is the NamespacedName of the HTTPRoute the group belongs to. - Source types.NamespacedName - // Backends is a list of Backends in the Group. - Backends []Backend - // RuleIdx is the index of the corresponding rule in the HTTPRoute. - RuleIdx int -} - -// Name returns the name of the backend group. -// This name must be unique across all HTTPRoutes and all rules within the same HTTPRoute. -// The RuleIdx is used to make the name unique across all rules within the same HTTPRoute. -// The RuleIdx may change for a given rule if an update is made to the HTTPRoute, but it will always match the index -// of the rule in the stored HTTPRoute. -func (bg *BackendGroup) Name() string { - return fmt.Sprintf("%s__%s_rule%d", bg.Source.Namespace, bg.Source.Name, bg.RuleIdx) -} - -// Backend represents a Backend for a routing rule. -type Backend struct { - // UpstreamName is the name of the upstream for this backend. - UpstreamName string - // Weight is the weight of the BackendRef. - // The possible values of weight are 0-1,000,000. - // If weight is 0, no traffic should be forwarded for this entry. - Weight int32 - // Valid indicates whether the Backend is valid. - Valid bool -} - -// GetMatch returns the HTTPRouteMatch of the Route . -func (r *MatchRule) GetMatch() v1beta1.HTTPRouteMatch { - return r.Source.Spec.Rules[r.RuleIdx].Matches[r.MatchIdx] -} +const wildcardHostname = "~^" // BuildConfiguration builds the Configuration from the Graph. func BuildConfiguration(ctx context.Context, g *graph.Graph, resolver resolver.ServiceResolver) Configuration { @@ -366,17 +224,17 @@ func (hpr *hostPathRules) upsertListener(l *graph.Listener) { continue } - var filters Filters + var filters HTTPFilters if r.Rules[i].ValidFilters { - filters = createFilters(rule.Filters) + filters = createHTTPFilters(rule.Filters) } else { - filters = Filters{ - InvalidFilter: &InvalidFilter{}, + filters = HTTPFilters{ + InvalidFilter: &InvalidHTTPFilter{}, } } for _, h := range hostnames { - for j, m := range rule.Matches { + for _, m := range rule.Matches { path := getPath(m.Path) key := pathAndType{ @@ -391,11 +249,10 @@ func (hpr *hostPathRules) upsertListener(l *graph.Listener) { } rule.MatchRules = append(rule.MatchRules, MatchRule{ - MatchIdx: j, - RuleIdx: i, - Source: r.Source, + Source: &r.Source.ObjectMeta, BackendGroup: newBackendGroup(r.Rules[i].BackendRefs, routeNsName, i), Filters: filters, + Match: convertMatch(m), }) hpr.rulesPerHost[h][key] = rule @@ -564,52 +421,26 @@ func getPath(path *v1beta1.HTTPPathMatch) string { return *path.Value } -func createFilters(filters []v1beta1.HTTPRouteFilter) Filters { - var result Filters +func createHTTPFilters(filters []v1beta1.HTTPRouteFilter) HTTPFilters { + var result HTTPFilters for _, f := range filters { switch f.Type { case v1beta1.HTTPRouteFilterRequestRedirect: if result.RequestRedirect == nil { // using the first filter - result.RequestRedirect = f.RequestRedirect + result.RequestRedirect = convertHTTPRequestRedirectFilter(f.RequestRedirect) } case v1beta1.HTTPRouteFilterRequestHeaderModifier: if result.RequestHeaderModifiers == nil { // using the first filter - result.RequestHeaderModifiers = convertHTTPFilter(f.RequestHeaderModifier) + result.RequestHeaderModifiers = convertHTTPHeaderFilter(f.RequestHeaderModifier) } } } return result } -func convertHTTPFilter(httpFilter *v1beta1.HTTPHeaderFilter) *HTTPHeaderFilter { - result := &HTTPHeaderFilter{ - Remove: httpFilter.Remove, - Set: make([]HTTPHeader, 0, len(httpFilter.Set)), - Add: make([]HTTPHeader, 0, len(httpFilter.Add)), - } - for _, s := range httpFilter.Set { - result.Set = append(result.Set, HTTPHeader{Name: string(s.Name), Value: s.Value}) - } - for _, a := range httpFilter.Add { - result.Add = append(result.Add, HTTPHeader{Name: string(a.Name), Value: a.Value}) - } - return result -} - -func convertPathType(pathType v1beta1.PathMatchType) PathType { - switch pathType { - case v1beta1.PathMatchPathPrefix: - return PathTypePrefix - case v1beta1.PathMatchExact: - return PathTypeExact - default: - panic(fmt.Sprintf("unsupported path type: %s", pathType)) - } -} - // listenerHostnameMoreSpecific returns true if host1 is more specific than host2. func listenerHostnameMoreSpecific(host1, host2 *v1beta1.Hostname) bool { var host1Str, host2Str string diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index 94a934996..3656fc168 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -6,7 +6,6 @@ import ( "fmt" "testing" - "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -223,6 +222,9 @@ func TestBuildConfiguration(t *testing.T) { }, } addFilters(hr5, []v1beta1.HTTPRouteFilter{redirect}) + expRedirect := HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("foo.example.com"), + } hr6, expHR6Groups, routeHR6 := createTestResources( "hr-6", @@ -592,10 +594,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR2Groups[0], - Source: hr2, + Source: &hr2.ObjectMeta, }, }, }, @@ -610,10 +610,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR1Groups[0], - Source: hr1, + Source: &hr1.ObjectMeta, }, }, }, @@ -681,10 +679,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR2Groups[0], - Source: httpsHR2, + Source: &httpsHR2.ObjectMeta, }, }, }, @@ -700,10 +696,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR5Groups[0], - Source: httpsHR5, + Source: &httpsHR5.ObjectMeta, }, }, }, @@ -719,10 +713,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR1Groups[0], - Source: httpsHR1, + Source: &httpsHR1.ObjectMeta, }, }, }, @@ -803,16 +795,12 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR3Groups[0], - Source: hr3, + Source: &hr3.ObjectMeta, }, { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHR4Groups[1], - Source: hr4, + Source: &hr4.ObjectMeta, }, }, }, @@ -821,10 +809,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR4Groups[0], - Source: hr4, + Source: &hr4.ObjectMeta, }, }, }, @@ -833,10 +819,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHR3Groups[1], - Source: hr3, + Source: &hr3.ObjectMeta, }, }, }, @@ -858,16 +842,12 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR3Groups[0], - Source: httpsHR3, + Source: &httpsHR3.ObjectMeta, }, { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHTTPSHR4Groups[1], - Source: httpsHR4, + Source: &httpsHR4.ObjectMeta, }, }, }, @@ -876,10 +856,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR4Groups[0], - Source: httpsHR4, + Source: &httpsHR4.ObjectMeta, }, }, }, @@ -888,10 +866,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHTTPSHR3Groups[1], - Source: httpsHR3, + Source: &httpsHR3.ObjectMeta, }, }, }, @@ -989,10 +965,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR3Groups[0], - Source: hr3, + Source: &hr3.ObjectMeta, }, }, }, @@ -1001,10 +975,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHR3Groups[1], - Source: hr3, + Source: &hr3.ObjectMeta, }, }, }, @@ -1023,10 +995,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR8Groups[0], - Source: hr8, + Source: &hr8.ObjectMeta, }, }, }, @@ -1035,10 +1005,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHR8Groups[1], - Source: hr8, + Source: &hr8.ObjectMeta, }, }, }, @@ -1060,10 +1028,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR3Groups[0], - Source: httpsHR3, + Source: &httpsHR3.ObjectMeta, }, }, }, @@ -1072,10 +1038,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHTTPSHR3Groups[1], - Source: httpsHR3, + Source: &httpsHR3.ObjectMeta, }, }, }, @@ -1100,10 +1064,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR7Groups[0], - Source: httpsHR7, + Source: &httpsHR7.ObjectMeta, }, }, }, @@ -1112,10 +1074,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHTTPSHR7Groups[1], - Source: httpsHR7, + Source: &httpsHR7.ObjectMeta, }, }, }, @@ -1244,12 +1204,10 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, - Source: hr5, + Source: &hr5.ObjectMeta, BackendGroup: expHR5Groups[0], - Filters: Filters{ - RequestRedirect: redirect.RequestRedirect, + Filters: HTTPFilters{ + RequestRedirect: &expRedirect, }, }, }, @@ -1259,12 +1217,10 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, - Source: hr5, + Source: &hr5.ObjectMeta, BackendGroup: expHR5Groups[1], - Filters: Filters{ - InvalidFilter: &InvalidFilter{}, + Filters: HTTPFilters{ + InvalidFilter: &InvalidHTTPFilter{}, }, }, }, @@ -1328,10 +1284,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR6Groups[0], - Source: hr6, + Source: &hr6.ObjectMeta, }, }, }, @@ -1353,10 +1307,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR6Groups[0], - Source: httpsHR6, + Source: &httpsHR6.ObjectMeta, }, }, }, @@ -1419,10 +1371,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypeExact, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 1, BackendGroup: expHR7Groups[1], - Source: hr7, + Source: &hr7.ObjectMeta, }, }, }, @@ -1431,10 +1381,8 @@ func TestBuildConfiguration(t *testing.T) { PathType: PathTypePrefix, MatchRules: []MatchRule{ { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHR7Groups[0], - Source: hr7, + Source: &hr7.ObjectMeta, }, }, }, @@ -1500,16 +1448,12 @@ func TestBuildConfiguration(t *testing.T) { MatchRules: []MatchRule{ // duplicate match rules since two listeners both match this route's hostname { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR5Groups[0], - Source: httpsHR5, + Source: &httpsHR5.ObjectMeta, }, { - MatchIdx: 0, - RuleIdx: 0, BackendGroup: expHTTPSHR5Groups[0], - Source: httpsHR5, + Source: &httpsHR5.ObjectMeta, }, }, }, @@ -1595,13 +1539,13 @@ func TestCreateFilters(t *testing.T) { redirect1 := v1beta1.HTTPRouteFilter{ Type: v1beta1.HTTPRouteFilterRequestRedirect, RequestRedirect: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: (*v1beta1.PreciseHostname)(helpers.GetStringPointer("foo.example.com")), + Hostname: helpers.GetPointer[v1beta1.PreciseHostname]("foo.example.com"), }, } redirect2 := v1beta1.HTTPRouteFilter{ Type: v1beta1.HTTPRouteFilterRequestRedirect, RequestRedirect: &v1beta1.HTTPRequestRedirectFilter{ - Hostname: (*v1beta1.PreciseHostname)(helpers.GetStringPointer("bar.example.com")), + Hostname: helpers.GetPointer[v1beta1.PreciseHostname]("bar.example.com"), }, } requestHeaderModifiers1 := v1beta1.HTTPRouteFilter{ @@ -1627,22 +1571,34 @@ func TestCreateFilters(t *testing.T) { }, } + expectedRedirect1 := HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer("foo.example.com"), + } + expectedHeaderModifier1 := HTTPHeaderFilter{ + Set: []HTTPHeader{ + { + Name: "MyBespokeHeader", + Value: "my-value", + }, + }, + } + tests := []struct { - expected Filters + expected HTTPFilters msg string filters []v1beta1.HTTPRouteFilter }{ { filters: []v1beta1.HTTPRouteFilter{}, - expected: Filters{}, + expected: HTTPFilters{}, msg: "no filters", }, { filters: []v1beta1.HTTPRouteFilter{ redirect1, }, - expected: Filters{ - RequestRedirect: redirect1.RequestRedirect, + expected: HTTPFilters{ + RequestRedirect: &expectedRedirect1, }, msg: "one filter", }, @@ -1651,8 +1607,8 @@ func TestCreateFilters(t *testing.T) { redirect1, redirect2, }, - expected: Filters{ - RequestRedirect: redirect1.RequestRedirect, + expected: HTTPFilters{ + RequestRedirect: &expectedRedirect1, }, msg: "two filters, first wins", }, @@ -1662,9 +1618,9 @@ func TestCreateFilters(t *testing.T) { redirect2, requestHeaderModifiers1, }, - expected: Filters{ - RequestRedirect: redirect1.RequestRedirect, - RequestHeaderModifiers: convertHTTPFilter(requestHeaderModifiers1.RequestHeaderModifier), + expected: HTTPFilters{ + RequestRedirect: &expectedRedirect1, + RequestHeaderModifiers: &expectedHeaderModifier1, }, msg: "two redirect filters, one request header modifier, first redirect wins", }, @@ -1675,90 +1631,21 @@ func TestCreateFilters(t *testing.T) { requestHeaderModifiers1, requestHeaderModifiers2, }, - expected: Filters{ - RequestRedirect: redirect1.RequestRedirect, - RequestHeaderModifiers: convertHTTPFilter(requestHeaderModifiers1.RequestHeaderModifier), + expected: HTTPFilters{ + RequestRedirect: &expectedRedirect1, + RequestHeaderModifiers: &expectedHeaderModifier1, }, msg: "two redirect filters, two request header modifier, first value for each wins", }, } for _, test := range tests { - result := createFilters(test.filters) - if diff := cmp.Diff(test.expected, result); diff != "" { - t.Errorf("createFilters() %q mismatch (-want +got):\n%s", test.msg, diff) - } - } -} - -func TestMatchRuleGetMatch(t *testing.T) { - hr := &v1beta1.HTTPRoute{ - Spec: v1beta1.HTTPRouteSpec{ - Rules: []v1beta1.HTTPRouteRule{ - { - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-1"), - }, - }, - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-2"), - }, - }, - }, - }, - { - Matches: []v1beta1.HTTPRouteMatch{ - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-3"), - }, - }, - { - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path-4"), - }, - }, - }, - }, - }, - }, - } - - tests := []struct { - name string - expPath string - rule MatchRule - }{ - { - name: "first match in first rule", - expPath: "/path-1", - rule: MatchRule{MatchIdx: 0, RuleIdx: 0, Source: hr}, - }, - { - name: "second match in first rule", - expPath: "/path-2", - rule: MatchRule{MatchIdx: 1, RuleIdx: 0, Source: hr}, - }, - { - name: "second match in second rule", - expPath: "/path-4", - rule: MatchRule{MatchIdx: 1, RuleIdx: 1, Source: hr}, - }, - } + t.Run(test.msg, func(t *testing.T) { + g := NewGomegaWithT(t) + result := createHTTPFilters(test.filters) - for _, tc := range tests { - actual := tc.rule.GetMatch() - if *actual.Path.Value != tc.expPath { - t.Errorf( - "MatchRule.GetMatch() returned incorrect match with path: %s, expected path: %s for test case: %q", - *actual.Path.Value, - tc.expPath, - tc.name, - ) - } + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } @@ -2061,38 +1948,6 @@ func TestBuildBackendGroups(t *testing.T) { g.Expect(result).To(ConsistOf(expGroups)) } -func TestConvertPathType(t *testing.T) { - g := NewGomegaWithT(t) - - tests := []struct { - expected PathType - pathType v1beta1.PathMatchType - panic bool - }{ - { - expected: PathTypePrefix, - pathType: v1beta1.PathMatchPathPrefix, - }, - { - expected: PathTypeExact, - pathType: v1beta1.PathMatchExact, - }, - { - pathType: v1beta1.PathMatchRegularExpression, - panic: true, - }, - } - - for _, tc := range tests { - if tc.panic { - g.Expect(func() { convertPathType(tc.pathType) }).To(Panic()) - } else { - result := convertPathType(tc.pathType) - g.Expect(result).To(Equal(tc.expected)) - } - } -} - func TestHostnameMoreSpecific(t *testing.T) { tests := []struct { host1 *v1beta1.Hostname @@ -2152,34 +2007,3 @@ func TestHostnameMoreSpecific(t *testing.T) { }) } } - -func TestConvertHTTPFilter(t *testing.T) { - g := NewGomegaWithT(t) - - httpFilter := &v1beta1.HTTPHeaderFilter{ - Set: []v1beta1.HTTPHeader{{ - Name: "My-Set-Header", - Value: "my-value", - }}, - Add: []v1beta1.HTTPHeader{{ - Name: "My-Add-Header", - Value: "my-value", - }}, - Remove: []string{"My-remove-header"}, - } - - expected := HTTPHeaderFilter{ - Set: []HTTPHeader{{ - Name: "My-Set-Header", - Value: "my-value", - }}, - Add: []HTTPHeader{{ - Name: "My-Add-Header", - Value: "my-value", - }}, - Remove: []string{"My-remove-header"}, - } - - result := convertHTTPFilter(httpFilter) - g.Expect(*result).To(Equal(expected)) -} diff --git a/internal/mode/static/state/dataplane/convert.go b/internal/mode/static/state/dataplane/convert.go new file mode 100644 index 000000000..d96e763c7 --- /dev/null +++ b/internal/mode/static/state/dataplane/convert.go @@ -0,0 +1,80 @@ +package dataplane + +import ( + "fmt" + + "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func convertMatch(m v1beta1.HTTPRouteMatch) Match { + match := Match{} + + if m.Method != nil { + method := string(*m.Method) + match.Method = &method + } + + if len(m.Headers) != 0 { + match.Headers = make([]HTTPHeaderMatch, 0, len(m.Headers)) + for _, h := range m.Headers { + match.Headers = append(match.Headers, HTTPHeaderMatch{ + Name: string(h.Name), + Value: h.Value, + }) + } + } + + if len(m.QueryParams) != 0 { + match.QueryParams = make([]HTTPQueryParamMatch, 0, len(m.QueryParams)) + for _, q := range m.QueryParams { + match.QueryParams = append(match.QueryParams, HTTPQueryParamMatch{ + Name: string(q.Name), + Value: q.Value, + }) + } + } + + return match +} + +func convertHTTPRequestRedirectFilter(filter *v1beta1.HTTPRequestRedirectFilter) *HTTPRequestRedirectFilter { + return &HTTPRequestRedirectFilter{ + Scheme: filter.Scheme, + Hostname: (*string)(filter.Hostname), + Port: (*int32)(filter.Port), + StatusCode: filter.StatusCode, + } +} + +func convertHTTPHeaderFilter(filter *v1beta1.HTTPHeaderFilter) *HTTPHeaderFilter { + result := &HTTPHeaderFilter{ + Remove: filter.Remove, + } + + if len(filter.Set) != 0 { + result.Set = make([]HTTPHeader, 0, len(filter.Set)) + for _, s := range filter.Set { + result.Set = append(result.Set, HTTPHeader{Name: string(s.Name), Value: s.Value}) + } + } + + if len(filter.Add) != 0 { + result.Add = make([]HTTPHeader, 0, len(filter.Add)) + for _, a := range filter.Add { + result.Add = append(result.Add, HTTPHeader{Name: string(a.Name), Value: a.Value}) + } + } + + return result +} + +func convertPathType(pathType v1beta1.PathMatchType) PathType { + switch pathType { + case v1beta1.PathMatchPathPrefix: + return PathTypePrefix + case v1beta1.PathMatchExact: + return PathTypeExact + default: + panic(fmt.Sprintf("unsupported path type: %s", pathType)) + } +} diff --git a/internal/mode/static/state/dataplane/convert_test.go b/internal/mode/static/state/dataplane/convert_test.go new file mode 100644 index 000000000..1d167b978 --- /dev/null +++ b/internal/mode/static/state/dataplane/convert_test.go @@ -0,0 +1,242 @@ +package dataplane + +import ( + "testing" + + . "github.com/onsi/gomega" + "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/framework/helpers" +) + +func TestConvertMatch(t *testing.T) { + path := v1beta1.HTTPPathMatch{ + Type: helpers.GetPointer(v1beta1.PathMatchPathPrefix), + Value: helpers.GetPointer("/"), + } + + tests := []struct { + match v1beta1.HTTPRouteMatch + name string + expected Match + }{ + { + match: v1beta1.HTTPRouteMatch{ + Path: &path, + }, + expected: Match{}, + name: "path only", + }, + { + match: v1beta1.HTTPRouteMatch{ + Path: &path, + Method: helpers.GetPointer(v1beta1.HTTPMethodGet), + }, + expected: Match{ + Method: helpers.GetPointer("GET"), + }, + name: "path and method", + }, + { + match: v1beta1.HTTPRouteMatch{ + Path: &path, + Headers: []v1beta1.HTTPHeaderMatch{ + { + Name: "Test-Header", + Value: "test-header-value", + }, + }, + }, + expected: Match{ + Headers: []HTTPHeaderMatch{ + { + Name: "Test-Header", + Value: "test-header-value", + }, + }, + }, + name: "path and header", + }, + { + match: v1beta1.HTTPRouteMatch{ + Path: &path, + QueryParams: []v1beta1.HTTPQueryParamMatch{ + { + Name: "Test-Param", + Value: "test-param-value", + }, + }, + }, + expected: Match{ + QueryParams: []HTTPQueryParamMatch{ + { + Name: "Test-Param", + Value: "test-param-value", + }, + }, + }, + name: "path and query param", + }, + { + match: v1beta1.HTTPRouteMatch{ + Path: &path, + Method: helpers.GetPointer(v1beta1.HTTPMethodGet), + Headers: []v1beta1.HTTPHeaderMatch{ + { + Name: "Test-Header", + Value: "test-header-value", + }, + }, + QueryParams: []v1beta1.HTTPQueryParamMatch{ + { + Name: "Test-Param", + Value: "test-param-value", + }, + }, + }, + expected: Match{ + Method: helpers.GetPointer("GET"), + Headers: []HTTPHeaderMatch{ + { + Name: "Test-Header", + Value: "test-header-value", + }, + }, + QueryParams: []HTTPQueryParamMatch{ + { + Name: "Test-Param", + Value: "test-param-value", + }, + }, + }, + name: "path, method, header, and query param", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := convertMatch(test.match) + g.Expect(helpers.Diff(result, test.expected)).To(BeEmpty()) + }) + } +} + +func TestConvertHTTPRequestRedirectFilter(t *testing.T) { + tests := []struct { + filter *v1beta1.HTTPRequestRedirectFilter + expected *HTTPRequestRedirectFilter + name string + }{ + { + filter: &v1beta1.HTTPRequestRedirectFilter{}, + expected: &HTTPRequestRedirectFilter{}, + name: "empty", + }, + { + filter: &v1beta1.HTTPRequestRedirectFilter{ + Scheme: helpers.GetPointer("https"), + Hostname: helpers.GetPointer[v1beta1.PreciseHostname]("example.com"), + Port: helpers.GetPointer[v1beta1.PortNumber](8443), + StatusCode: helpers.GetPointer(302), + }, + expected: &HTTPRequestRedirectFilter{ + Scheme: helpers.GetPointer("https"), + Hostname: helpers.GetPointer("example.com"), + Port: helpers.GetPointer[int32](8443), + StatusCode: helpers.GetPointer(302), + }, + name: "full", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := convertHTTPRequestRedirectFilter(test.filter) + g.Expect(result).To(Equal(test.expected)) + }) + } +} + +func TestConvertHTTPHeaderFilter(t *testing.T) { + tests := []struct { + filter *v1beta1.HTTPHeaderFilter + expected *HTTPHeaderFilter + name string + }{ + { + filter: &v1beta1.HTTPHeaderFilter{}, + expected: &HTTPHeaderFilter{}, + name: "empty", + }, + { + filter: &v1beta1.HTTPHeaderFilter{ + Set: []v1beta1.HTTPHeader{{ + Name: "My-Set-Header", + Value: "my-value", + }}, + Add: []v1beta1.HTTPHeader{{ + Name: "My-Add-Header", + Value: "my-value", + }}, + Remove: []string{"My-remove-header"}, + }, + expected: &HTTPHeaderFilter{ + Set: []HTTPHeader{{ + Name: "My-Set-Header", + Value: "my-value", + }}, + Add: []HTTPHeader{{ + Name: "My-Add-Header", + Value: "my-value", + }}, + Remove: []string{"My-remove-header"}, + }, + name: "full", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewGomegaWithT(t) + + result := convertHTTPHeaderFilter(test.filter) + g.Expect(result).To(Equal(test.expected)) + }) + } +} + +func TestConvertPathType(t *testing.T) { + g := NewGomegaWithT(t) + + tests := []struct { + pathType v1beta1.PathMatchType + expected PathType + panic bool + }{ + { + expected: PathTypePrefix, + pathType: v1beta1.PathMatchPathPrefix, + }, + { + expected: PathTypeExact, + pathType: v1beta1.PathMatchExact, + }, + { + pathType: v1beta1.PathMatchRegularExpression, + panic: true, + }, + } + + for _, tc := range tests { + if tc.panic { + g.Expect(func() { convertPathType(tc.pathType) }).To(Panic()) + } else { + result := convertPathType(tc.pathType) + g.Expect(result).To(Equal(tc.expected)) + } + } +} diff --git a/internal/mode/static/state/dataplane/sort.go b/internal/mode/static/state/dataplane/sort.go index 7227fb384..23c4c19bf 100644 --- a/internal/mode/static/state/dataplane/sort.go +++ b/internal/mode/static/state/dataplane/sort.go @@ -37,39 +37,35 @@ If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria. higherPriority will determine precedence by comparing len(headers), len(query parameters), creation timestamp, -and namespace name. The other criteria are handled by NGINX. +and namespace name. It gives higher priority to rules with a method match. The other criteria are handled by NGINX. */ func higherPriority(rule1, rule2 MatchRule) bool { - // Get the matches from the rules - match1 := rule1.GetMatch() - match2 := rule2.GetMatch() - // Compare if a method exists on one of the matches but not the other. // The match with the method specified wins. - if match1.Method != nil && match2.Method == nil { + if rule1.Match.Method != nil && rule2.Match.Method == nil { return true } - if match2.Method != nil && match1.Method == nil { + if rule2.Match.Method != nil && rule1.Match.Method == nil { return false } // Compare the number of header matches. // The match with the largest number of header matches wins. - l1 := len(match1.Headers) - l2 := len(match2.Headers) + l1 := len(rule1.Match.Headers) + l2 := len(rule2.Match.Headers) if l1 != l2 { return l1 > l2 } // If the number of headers is equal then compare the number of query param matches. // The match with the most query param matches wins. - l1 = len(match1.QueryParams) - l2 = len(match2.QueryParams) + l1 = len(rule1.Match.QueryParams) + l2 = len(rule2.Match.QueryParams) if l1 != l2 { return l1 > l2 } // If still tied, compare the object meta of the two routes. - return nkgsort.LessObjectMeta(&rule1.Source.ObjectMeta, &rule2.Source.ObjectMeta) + return nkgsort.LessObjectMeta(rule1.Source, rule2.Source) } diff --git a/internal/mode/static/state/dataplane/sort_test.go b/internal/mode/static/state/dataplane/sort_test.go index 9a2c3d417..ab8ad5ce5 100644 --- a/internal/mode/static/state/dataplane/sort_test.go +++ b/internal/mode/static/state/dataplane/sort_test.go @@ -5,8 +5,8 @@ import ( "time" "github.com/google/go-cmp/cmp" + . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/nginxinc/nginx-kubernetes-gateway/internal/framework/helpers" ) @@ -16,208 +16,148 @@ func TestSort(t *testing.T) { earlier := metav1.Now() later := metav1.NewTime(earlier.Add(1 * time.Second)) - // matches - pathOnlyMatch := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), // path match only (low priority) - }, + earlierTimestampMeta := &metav1.ObjectMeta{ + Name: "hr1", + Namespace: "test", + CreationTimestamp: earlier, } - twoHeaderMatch := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Headers: []v1beta1.HTTPHeaderMatch{ - { - Name: "header1", - Value: "value1", - }, - { - Name: "header2", - Value: "value2", - }, - }, + laterTimestampMeta := &metav1.ObjectMeta{ + Name: "hr2", + Namespace: "test", + CreationTimestamp: later, } - threeHeaderMatch := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Headers: []v1beta1.HTTPHeaderMatch{ - { - Name: "header1", - Value: "value1", - }, - { - Name: "header2", - Value: "value2", - }, - { - Name: "header3", - Value: "value3", + laterTimestampButAlphabeticallyFirstMeta := &metav1.ObjectMeta{ + Name: "hr3", + Namespace: "a-test", + CreationTimestamp: later, + } + + pathOnly := MatchRule{ + Match: Match{}, + Source: earlierTimestampMeta, + } + twoHeadersEarlierTimestamp := MatchRule{ + Match: Match{ + Headers: []HTTPHeaderMatch{ + { + Name: "header1", + Value: "value1", + }, + { + Name: "header2", + Value: "value2", + }, }, }, + Source: earlierTimestampMeta, } - twoHeaderOneParamMatch := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Headers: []v1beta1.HTTPHeaderMatch{ - { - Name: "header1", - Value: "value1", - }, - { - Name: "header2", - Value: "value2", + twoHeadersOneParam := MatchRule{ + Match: Match{ + Headers: []HTTPHeaderMatch{ + { + Name: "header1", + Value: "value1", + }, + { + Name: "header2", + Value: "value2", + }, }, - }, - QueryParams: []v1beta1.HTTPQueryParamMatch{ - { - Name: "key1", - Value: "value1", + QueryParams: []HTTPQueryParamMatch{ + { + Name: "key1", + Value: "value1", + }, }, }, + Source: earlierTimestampMeta, } - methodMatch := v1beta1.HTTPRouteMatch{ - Path: &v1beta1.HTTPPathMatch{ - Value: helpers.GetStringPointer("/path"), - }, - Method: helpers.GetPointer(v1beta1.HTTPMethodPost), - } - - hr1 := v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "hr1", - Namespace: "test", - CreationTimestamp: earlier, - }, - Spec: v1beta1.HTTPRouteSpec{ - Rules: []v1beta1.HTTPRouteRule{ + threeHeaders := MatchRule{ + Match: Match{ + Headers: []HTTPHeaderMatch{ { - Matches: []v1beta1.HTTPRouteMatch{pathOnlyMatch}, + Name: "header1", + Value: "value1", }, { - Matches: []v1beta1.HTTPRouteMatch{twoHeaderMatch}, + Name: "header2", + Value: "value2", }, { - Matches: []v1beta1.HTTPRouteMatch{ - twoHeaderOneParamMatch, // tie decided on params - threeHeaderMatch, // tie decided on headers - methodMatch, // tie decided on method - }, + Name: "header3", + Value: "value3", }, }, }, + Source: earlierTimestampMeta, } - - hr2 := v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "hr2", - Namespace: "test", - CreationTimestamp: later, + methodEarlierTimestamp := MatchRule{ + Match: Match{ + Method: helpers.GetPointer("POST"), + }, + Source: earlierTimestampMeta, + } + methodLaterTimestamp := MatchRule{ + Match: Match{ + Method: helpers.GetPointer("POST"), }, - Spec: v1beta1.HTTPRouteSpec{ - Rules: []v1beta1.HTTPRouteRule{ + Source: earlierTimestampMeta, + } + twoHeadersLaterTimestamp := MatchRule{ + Match: Match{ + Headers: []HTTPHeaderMatch{ + { + Name: "header1", + Value: "value1", + }, { - Matches: []v1beta1.HTTPRouteMatch{twoHeaderMatch}, // tie decided on creation timestamp + Name: "header2", + Value: "value2", }, }, }, + Source: laterTimestampMeta, } - - hr3 := v1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "hr3", - Namespace: "a-test", // tie decided by namespace name - CreationTimestamp: later, - }, - Spec: v1beta1.HTTPRouteSpec{ - Rules: []v1beta1.HTTPRouteRule{ + twoHeadersLaterTimestampButAlphabeticallyBefore := MatchRule{ + Match: Match{ + Headers: []HTTPHeaderMatch{ + { + Name: "header1", + Value: "value1", + }, { - Matches: []v1beta1.HTTPRouteMatch{twoHeaderMatch}, + Name: "header2", + Value: "value2", }, }, }, + Source: laterTimestampButAlphabeticallyFirstMeta, } - routes := []MatchRule{ - { - MatchIdx: 0, // pathOnlyMatch - RuleIdx: 0, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderMatch / earlier timestamp - RuleIdx: 1, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderOneParamMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 1, // threeHeaderMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 2, // methodMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderMatch / later timestamp / test/hr2 - RuleIdx: 0, - Source: &hr2, - }, - { - MatchIdx: 0, // twoHeaderMatch / later timestamp / a-test/hr3 - RuleIdx: 0, - Source: &hr3, - }, + rules := []MatchRule{ + methodLaterTimestamp, + pathOnly, + twoHeadersEarlierTimestamp, + twoHeadersOneParam, + threeHeaders, + methodEarlierTimestamp, + twoHeadersLaterTimestamp, + twoHeadersLaterTimestampButAlphabeticallyBefore, } - sortedRoutes := []MatchRule{ - { - MatchIdx: 2, // methodMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 1, // threeHeaderMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderOneParamMatch - RuleIdx: 2, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderMatch / earlier timestamp - RuleIdx: 1, - Source: &hr1, - }, - { - MatchIdx: 0, // twoHeaderMatch / later timestamp / a-test/hr3 - RuleIdx: 0, - Source: &hr3, - }, - { - MatchIdx: 0, // twoHeaderMatch / later timestamp / test/hr2 - RuleIdx: 0, - Source: &hr2, - }, - { - MatchIdx: 0, // pathOnlyMatch - RuleIdx: 0, - Source: &hr1, - }, + sortedRules := []MatchRule{ + methodEarlierTimestamp, + methodLaterTimestamp, + threeHeaders, + twoHeadersOneParam, + twoHeadersEarlierTimestamp, + twoHeadersLaterTimestampButAlphabeticallyBefore, + twoHeadersLaterTimestamp, + pathOnly, } - sortMatchRules(routes) + sortMatchRules(rules) - if diff := cmp.Diff(sortedRoutes, routes); diff != "" { - t.Errorf("sortMatchRules() mismatch (-want +got):\n%s", diff) - } + g := NewGomegaWithT(t) + g.Expect(cmp.Diff(sortedRules, rules)).To(BeEmpty()) } diff --git a/internal/mode/static/state/dataplane/types.go b/internal/mode/static/state/dataplane/types.go new file mode 100644 index 000000000..e6ee3f0d5 --- /dev/null +++ b/internal/mode/static/state/dataplane/types.go @@ -0,0 +1,202 @@ +package dataplane + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/mode/static/state/resolver" +) + +// PathType is the type of the path in a PathRule. +type PathType string + +const ( + // PathTypePrefix indicates that the path is a prefix. + PathTypePrefix PathType = "prefix" + // PathTypeExact indicates that the path is exact. + PathTypeExact PathType = "exact" +) + +// Configuration is an intermediate representation of dataplane configuration. +type Configuration struct { + // SSLKeyPairs holds all unique SSLKeyPairs. + SSLKeyPairs map[SSLKeyPairID]SSLKeyPair + // HTTPServers holds all HTTPServers. + HTTPServers []VirtualServer + // SSLServers holds all SSLServers. + SSLServers []VirtualServer + // Upstreams holds all unique Upstreams. + Upstreams []Upstream + // BackendGroups holds all unique BackendGroups. + BackendGroups []BackendGroup +} + +// SSLKeyPairID is a unique identifier for a SSLKeyPair. +// The ID is safe to use as a file name. +type SSLKeyPairID string + +// SSLKeyPair is an SSL private/public key pair. +type SSLKeyPair struct { + // Cert is the certificate. + Cert []byte + // Key is the private key. + Key []byte +} + +// VirtualServer is a virtual server. +type VirtualServer struct { + // SSL holds the SSL configuration for the server. + SSL *SSL + // Hostname is the hostname of the server. + Hostname string + // PathRules is a collection of routing rules. + PathRules []PathRule + // IsDefault indicates whether the server is the default server. + IsDefault bool + // Port is the port of the server. + Port int32 +} + +// Upstream is a pool of endpoints to be load balanced. +type Upstream struct { + // Name is the name of the Upstream. Will be unique for each service/port combination. + Name string + // ErrorMsg contains the error message if the Upstream is invalid. + ErrorMsg string + // Endpoints are the endpoints of the Upstream. + Endpoints []resolver.Endpoint +} + +// SSL is the SSL configuration for a server. +type SSL struct { + // KeyPairID is the ID of the corresponding SSLKeyPair for the server. + KeyPairID SSLKeyPairID +} + +// PathRule represents routing rules that share a common path. +type PathRule struct { + // Path is a path. For example, '/hello'. + Path string + // PathType is the type of the path. + PathType PathType + // MatchRules holds routing rules. + MatchRules []MatchRule +} + +// InvalidHTTPFilter is a special filter for handling the case when configured filters are invalid. +type InvalidHTTPFilter struct{} + +// HTTPFilters hold the filters for a MatchRule. +type HTTPFilters struct { + // InvalidFilter is a special filter that indicates whether the filters are invalid. If this is the case, + // the data plane must return 500 error, and all other filters are nil. + InvalidFilter *InvalidHTTPFilter + // RequestRedirect holds the HTTPRequestRedirectFilter. + RequestRedirect *HTTPRequestRedirectFilter + // RequestHeaderModifiers holds the HTTPHeaderFilter. + RequestHeaderModifiers *HTTPHeaderFilter +} + +// HTTPHeader represents an HTTP header. +type HTTPHeader struct { + // Name is the name of the header. + Name string + // Value is the value of the header. + Value string +} + +// HTTPHeaderFilter manipulates HTTP headers. +type HTTPHeaderFilter struct { + // Set adds or replaces headers. + Set []HTTPHeader + // Add adds headers. It appends to any existing values associated with a header name. + Add []HTTPHeader + // Remove removes headers. + Remove []string +} + +// HTTPRequestRedirectFilter redirects HTTP requests. +type HTTPRequestRedirectFilter struct { + // Scheme is the scheme of the redirect. + Scheme *string + // Hostname is the hostname of the redirect. + Hostname *string + // Port is the port of the redirect. + Port *int32 + // StatusCode is the HTTP status code of the redirect. + StatusCode *int +} + +// HTTPHeaderMatch matches an HTTP header. +type HTTPHeaderMatch struct { + // Name is the name of the header to match. + Name string + // Value is the value of the header to match. + Value string +} + +// HTTPQueryParamMatch matches an HTTP query parameter. +type HTTPQueryParamMatch struct { + // Name is the name of the query parameter to match. + Name string + // Value is the value of the query parameter to match. + Value string +} + +// MatchRule represents a routing rule. It corresponds directly to a Match in the HTTPRoute resource. +// An HTTPRoute is guaranteed to have at least one rule with one match. +// If no rule or match is specified by the user, the default rule {{path:{ type: "PathPrefix", value: "/"}}} +// is set by the schema. +type MatchRule struct { + // Filters holds the filters for the MatchRule. + Filters HTTPFilters + // Source is the ObjectMeta of the resource that includes the rule. + Source *metav1.ObjectMeta + // Match holds the match for the rule. + Match Match + // BackendGroup is the group of Backends that the rule routes to. + BackendGroup BackendGroup +} + +// Match represents a match for a routing rule which consist of matches against various HTTP request attributes. +type Match struct { + // Method matches against the HTTP method. + Method *string + // Headers matches against the HTTP headers. + Headers []HTTPHeaderMatch + // QueryParams matches against the HTTP query parameters. + QueryParams []HTTPQueryParamMatch +} + +// BackendGroup represents a group of Backends for a routing rule in an HTTPRoute. +type BackendGroup struct { + // Source is the NamespacedName of the HTTPRoute the group belongs to. + Source types.NamespacedName + // Backends is a list of Backends in the Group. + Backends []Backend + // RuleIdx is the index of the corresponding rule in the HTTPRoute. + RuleIdx int +} + +// Name returns the name of the backend group. +// This name must be unique across all HTTPRoutes and all rules within the same HTTPRoute. +// The RuleIdx is used to make the name unique across all rules within the same HTTPRoute. +// The RuleIdx may change for a given rule if an update is made to the HTTPRoute, but it will always match the index +// of the rule in the stored HTTPRoute. +func (bg *BackendGroup) Name() string { + return fmt.Sprintf("%s__%s_rule%d", bg.Source.Namespace, bg.Source.Name, bg.RuleIdx) +} + +// Backend represents a Backend for a routing rule. +type Backend struct { + // UpstreamName is the name of the upstream for this backend. + UpstreamName string + // Weight is the weight of the BackendRef. + // The possible values of weight are 0-1,000,000. + // If weight is 0, no traffic should be forwarded for this entry. + Weight int32 + // Valid indicates whether the Backend is valid. + Valid bool +}