Merge pull request #5324 from aledbf/exact

Add support for PathTypeExact
This commit is contained in:
Kubernetes Prow Robot 2020-04-23 09:41:11 -07:00 committed by GitHub
commit a51bc3e8ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 221 additions and 51 deletions

View file

@ -527,6 +527,12 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
addLoc := true
for _, loc := range server.Locations {
if loc.Path == nginxPath {
// Same paths but different types are allowed
// (same type means overlap in the path definition)
if *loc.PathType != *path.PathType {
break
}
addLoc = false
if !loc.IsDefBackend {
@ -543,6 +549,7 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
loc.Port = ups.Port
loc.Service = ups.Service
loc.Ingress = ing
locationApplyAnnotations(loc, anns)
if loc.Redirect.FromToWWW {
@ -556,9 +563,9 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
if addLoc {
klog.V(3).Infof("Adding location %q for server %q with upstream %q (Ingress %q)",
nginxPath, server.Hostname, ups.Name, ingKey)
loc := &ingress.Location{
Path: nginxPath,
PathType: path.PathType,
Backend: ups.Name,
IsDefBackend: false,
Service: ups.Service,
@ -953,12 +960,14 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
// initialize default server and root location
pathTypePrefix := networking.PathTypePrefix
servers[defServerName] = &ingress.Server{
Hostname: defServerName,
SSLCert: n.getDefaultSSLCertificate(),
Locations: []*ingress.Location{
{
Path: rootLocation,
PathType: &pathTypePrefix,
IsDefBackend: true,
Backend: du.Name,
Proxy: ngxProxy,
@ -1025,8 +1034,10 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
continue
}
pathTypePrefix := networking.PathTypePrefix
loc := &ingress.Location{
Path: rootLocation,
PathType: &pathTypePrefix,
IsDefBackend: true,
Backend: un,
Service: &apiv1.Service{},
@ -1311,7 +1322,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
break
}
if canMergeBackend(priUps, altUps) && loc.Path == path.Path {
if canMergeBackend(priUps, altUps) && loc.Path == path.Path && *loc.PathType == *path.PathType {
klog.V(2).Infof("matching backend %v found for alternative backend %v",
priUps.Name, altUps.Name)

View file

@ -257,6 +257,8 @@ func TestCheckIngress(t *testing.T) {
})
}
var pathPrefix = networking.PathTypePrefix
func TestMergeAlternativeBackends(t *testing.T) {
testCases := map[string]struct {
ingress *ingress.Ingress
@ -279,7 +281,8 @@ func TestMergeAlternativeBackends(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-canary",
ServicePort: intstr.IntOrString{
@ -314,8 +317,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "example.com",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -339,8 +343,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "example.com",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -360,7 +365,8 @@ func TestMergeAlternativeBackends(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "foo-http-svc-canary",
ServicePort: intstr.IntOrString{
@ -379,7 +385,8 @@ func TestMergeAlternativeBackends(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-canary",
ServicePort: intstr.IntOrString{
@ -426,8 +433,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "foo.bar",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-foo-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-foo-http-svc-80",
},
},
},
@ -435,8 +443,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "example.com",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -472,8 +481,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "example.com",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -493,7 +503,8 @@ func TestMergeAlternativeBackends(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-canary",
ServicePort: intstr.IntOrString{
@ -557,8 +568,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "_",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -582,8 +594,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "_",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "example-http-svc-80",
Path: "/",
PathType: &pathPrefix,
Backend: "example-http-svc-80",
},
},
},
@ -623,8 +636,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "_",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "upstream-default-backend",
Path: "/",
PathType: &pathPrefix,
Backend: "upstream-default-backend",
},
},
},
@ -635,8 +649,9 @@ func TestMergeAlternativeBackends(t *testing.T) {
Hostname: "_",
Locations: []*ingress.Location{
{
Path: "/",
Backend: "upstream-default-backend",
Path: "/",
PathType: &pathPrefix,
Backend: "upstream-default-backend",
},
},
},
@ -943,7 +958,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-canary",
ServicePort: intstr.IntOrString{
@ -1002,7 +1018,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc",
ServicePort: intstr.IntOrString{
@ -1038,7 +1055,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-canary",
ServicePort: intstr.IntOrString{
@ -1106,7 +1124,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/a",
Path: "/a",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-1",
ServicePort: intstr.IntOrString{
@ -1142,7 +1161,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/a",
Path: "/a",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-2",
ServicePort: intstr.IntOrString{
@ -1178,7 +1198,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/b",
Path: "/b",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-2",
ServicePort: intstr.IntOrString{
@ -1214,7 +1235,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/b",
Path: "/b",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-1",
ServicePort: intstr.IntOrString{
@ -1250,7 +1272,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/c",
Path: "/c",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-1",
ServicePort: intstr.IntOrString{
@ -1286,7 +1309,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/c",
Path: "/c",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "http-svc-2",
ServicePort: intstr.IntOrString{
@ -1370,7 +1394,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/path1",
Path: "/path1",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "path1-svc",
ServicePort: intstr.IntOrString{
@ -1409,7 +1434,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/path2",
Path: "/path2",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "path2-svc",
ServicePort: intstr.IntOrString{
@ -1473,7 +1499,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/path1",
Path: "/path1",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "path1-svc",
ServicePort: intstr.IntOrString{
@ -1512,7 +1539,8 @@ func TestGetBackendServers(t *testing.T) {
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/path2",
Path: "/path2",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
ServiceName: "path2-svc",
ServicePort: intstr.IntOrString{

View file

@ -26,8 +26,6 @@ import (
"sync"
"time"
"k8s.io/klog"
"github.com/eapache/channels"
corev1 "k8s.io/api/core/v1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
@ -36,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
@ -43,6 +42,7 @@ import (
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress"
@ -639,6 +639,9 @@ func isCatchAllIngress(spec networkingv1beta1.IngressSpec) bool {
return spec.Backend != nil && len(spec.Rules) == 0
}
// Default path type is Prefix to not break existing definitions
var defaultPathType = networkingv1beta1.PathTypePrefix
// syncIngress parses ingress annotations converting the value of the
// annotation to a go struct
func (s *k8sStore) syncIngress(ing *networkingv1beta1.Ingress) {
@ -659,6 +662,17 @@ func (s *k8sStore) syncIngress(ing *networkingv1beta1.Ingress) {
if path.Path == "" {
copyIng.Spec.Rules[ri].HTTP.Paths[pi].Path = "/"
}
if path.PathType == nil {
copyIng.Spec.Rules[ri].HTTP.Paths[pi].PathType = &defaultPathType
continue
}
// PathType ImplementationSpecific is not supported.
// Set type to PathTypePrefix.
if *path.PathType == networkingv1beta1.PathTypeImplementationSpecific {
copyIng.Spec.Rules[ri].HTTP.Paths[pi].PathType = &defaultPathType
}
}
}
@ -925,8 +939,8 @@ func (s k8sStore) GetRunningControllerPodsCount() int {
var runtimeScheme = k8sruntime.NewScheme()
func init() {
extensionsv1beta1.AddToScheme(runtimeScheme)
networkingv1beta1.AddToScheme(runtimeScheme)
utilruntime.Must(extensionsv1beta1.AddToScheme(runtimeScheme))
utilruntime.Must(networkingv1beta1.AddToScheme(runtimeScheme))
}
func fromExtensions(old *extensionsv1beta1.Ingress) (*networkingv1beta1.Ingress, error) {
@ -961,9 +975,6 @@ func toIngress(obj interface{}) (*networkingv1beta1.Ingress, bool) {
return nil, false
}
// Default path type is Prefix to not break existing definitions
var defaultPathType = networkingv1beta1.PathTypePrefix
func setDefaultPathTypeIfEmpty(ing *networkingv1beta1.Ingress) {
for _, rule := range ing.Spec.Rules {
if rule.IngressRuleValue.HTTP == nil {

View file

@ -39,6 +39,7 @@ import (
"github.com/pkg/errors"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog"
@ -402,6 +403,11 @@ func buildLocation(input interface{}, enforceRegex bool) string {
if enforceRegex {
return fmt.Sprintf(`~* "^%s"`, path)
}
if location.PathType != nil && *location.PathType == networkingv1beta1.PathTypeExact {
return fmt.Sprintf(`= %s`, path)
}
return path
}

View file

@ -224,6 +224,8 @@ type Location struct {
// a '/'. If unspecified, the path defaults to a catch all sending
// traffic to the backend.
Path string `json:"path"`
// PathType represents the type of path referred to by a HTTPIngressPath.
PathType *networking.PathType `json:"pathType"`
// IsDefBackend indicates if service specified in the Ingress
// contains active endpoints or not. Returning true means the location
// uses the default backend.

View file

@ -25,6 +25,7 @@ import (
"k8s.io/component-base/logs"
// required
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/ingress-nginx/test/e2e/framework"
@ -34,6 +35,7 @@ import (
_ "k8s.io/ingress-nginx/test/e2e/dbg"
_ "k8s.io/ingress-nginx/test/e2e/defaultbackend"
_ "k8s.io/ingress-nginx/test/e2e/gracefulshutdown"
_ "k8s.io/ingress-nginx/test/e2e/ingress"
_ "k8s.io/ingress-nginx/test/e2e/leaks"
_ "k8s.io/ingress-nginx/test/e2e/loadbalance"
_ "k8s.io/ingress-nginx/test/e2e/lua"

View file

@ -61,13 +61,6 @@ func Failf(format string, args ...interface{}) {
ginkgo.Fail(nowStamp()+": "+msg, 1)
}
// Skipf logs to the INFO logs and skips the test.
func Skipf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
log("INFO", msg)
ginkgo.Skip(nowStamp() + ": " + msg)
}
// RestclientConfig deserializes the contents of a kubeconfig file into a Config object.
func RestclientConfig(config, context string) (*api.Config, error) {
Logf(">>> config: %s\n", config)

View file

@ -0,0 +1,117 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ingress
import (
"net/http"
"strings"
"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.IngressNginxDescribe("[Ingress] [PathType] exact", func() {
f := framework.NewDefaultFramework("exact")
ginkgo.BeforeEach(func() {
f.NewEchoDeployment()
})
ginkgo.It("should choose exact location for /exact", func() {
if !f.IsIngressV1Ready {
ginkgo.Skip("Test requires Kubernetes v1.18 or higher")
}
host := "exact.path"
annotations := map[string]string{
"nginx.ingress.kubernetes.io/configuration-snippet": `more_set_input_headers "pathType: exact";`,
}
var exactPathType = networkingv1beta1.PathTypeExact
ing := framework.NewSingleIngress("exact", "/exact", host, f.Namespace, framework.EchoService, 80, annotations)
ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &exactPathType
f.EnsureIngress(ing)
annotations = map[string]string{
"nginx.ingress.kubernetes.io/configuration-snippet": `more_set_input_headers "pathType: prefix";`,
}
ing = framework.NewSingleIngress("exact-sufix", "/exact", host, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing)
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, host) &&
strings.Contains(server, "location = /exact") &&
strings.Contains(server, "location /exact")
})
body := f.HTTPTestClient().
GET("/exact").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).
Body().
Raw()
assert.NotContains(ginkgo.GinkgoT(), body, "pathtype=prefix")
assert.Contains(ginkgo.GinkgoT(), body, "pathtype=exact")
body = f.HTTPTestClient().
GET("/exact/sufix").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).
Body().
Raw()
assert.Contains(ginkgo.GinkgoT(), body, "pathtype=prefix")
annotations = map[string]string{
"nginx.ingress.kubernetes.io/configuration-snippet": `
more_set_input_headers "pathType: prefix";
more_set_input_headers "duplicated: true";
`,
}
ing = framework.NewSingleIngress("duplicated-prefix", "/exact", host, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing)
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, host) &&
strings.Contains(server, "location = /exact") &&
strings.Contains(server, "location /exact")
})
body = f.HTTPTestClient().
GET("/exact/sufix").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).
Body().
Raw()
assert.Contains(ginkgo.GinkgoT(), body, "pathtype=prefix")
assert.NotContains(ginkgo.GinkgoT(), body, "pathtype=exact")
assert.NotContains(ginkgo.GinkgoT(), body, "duplicated=true")
})
})