Add Knative provider
This commit is contained in:
parent
3f23afb2c6
commit
13bcdebc89
38 changed files with 18589 additions and 37 deletions
478
pkg/provider/kubernetes/knative/kubernetes_test.go
Normal file
478
pkg/provider/kubernetes/knative/kubernetes_test.go
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
package knative
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||
kscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/utils/ptr"
|
||||
knativenetworkingv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
|
||||
knfake "knative.dev/networking/pkg/client/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// required by k8s.MustParseYaml
|
||||
if err := knativenetworkingv1alpha1.AddToScheme(kscheme.Scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_loadConfiguration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
paths []string
|
||||
want *dynamic.Configuration
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
desc: "Wrong ingress class",
|
||||
paths: []string{"wrong_ingress_class.yaml"},
|
||||
wantLen: 0,
|
||||
want: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Cluster Local",
|
||||
paths: []string{"cluster_local.yaml", "services.yaml"},
|
||||
wantLen: 1,
|
||||
want: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-helloworld-go-rule-0-path-0": {
|
||||
EntryPoints: []string{"priv-http", "priv-https"},
|
||||
Service: "default-helloworld-go-rule-0-path-0-wrr",
|
||||
Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))",
|
||||
Middlewares: []string{},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-helloworld-go-rule-0-path-0-split-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.38.208:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-split-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.44.18:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-0",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00001",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-1",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "External IP",
|
||||
paths: []string{"external_ip.yaml", "services.yaml"},
|
||||
wantLen: 1,
|
||||
want: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-helloworld-go-rule-0-path-0": {
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Service: "default-helloworld-go-rule-0-path-0-wrr",
|
||||
Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))",
|
||||
Middlewares: []string{},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-helloworld-go-rule-0-path-0-split-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.38.208:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-split-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.44.18:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-0",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00001",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-1",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS",
|
||||
paths: []string{"tls.yaml", "services.yaml"},
|
||||
wantLen: 1,
|
||||
want: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-helloworld-go-rule-0-path-0": {
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Service: "default-helloworld-go-rule-0-path-0-wrr",
|
||||
Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))",
|
||||
Middlewares: []string{},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-tls": {
|
||||
EntryPoints: []string{"http", "https"},
|
||||
Service: "default-helloworld-go-rule-0-path-0-wrr",
|
||||
Rule: "(Host(`helloworld-go.default`) || Host(`helloworld-go.default.svc`) || Host(`helloworld-go.default.svc.cluster.local`))",
|
||||
Middlewares: []string{},
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-helloworld-go-rule-0-path-0-split-0": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.38.208:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-split-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: "wrr",
|
||||
PassHostHeader: ptr.To(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: types.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.43.44.18:80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-helloworld-go-rule-0-path-0-wrr": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-0",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00001",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "default-helloworld-go-rule-0-path-0-split-1",
|
||||
Weight: ptr.To(50),
|
||||
Headers: map[string]string{
|
||||
"Knative-Serving-Namespace": "default",
|
||||
"Knative-Serving-Revision": "helloworld-go-00002",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
k8sObjects, knObjects := readResources(t, testCase.paths)
|
||||
|
||||
k8sClient := kubefake.NewClientset(k8sObjects...)
|
||||
knClient := knfake.NewSimpleClientset(knObjects...)
|
||||
|
||||
client := newClientImpl(knClient, k8sClient)
|
||||
|
||||
eventCh, err := client.WatchAll(nil, make(chan struct{}))
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(k8sObjects) > 0 || len(knObjects) > 0 {
|
||||
// just wait for the first event
|
||||
<-eventCh
|
||||
}
|
||||
|
||||
p := Provider{
|
||||
PublicEntrypoints: []string{"http", "https"},
|
||||
PrivateEntrypoints: []string{"priv-http", "priv-https"},
|
||||
client: client,
|
||||
}
|
||||
|
||||
got, gotIngresses := p.loadConfiguration(t.Context())
|
||||
assert.Len(t, gotIngresses, testCase.wantLen)
|
||||
assert.Equal(t, testCase.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
hosts []string
|
||||
headers map[string]knativenetworkingv1alpha1.HeaderMatch
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
desc: "single host, no headers, no path",
|
||||
hosts: []string{"example.com"},
|
||||
want: "(Host(`example.com`))",
|
||||
},
|
||||
{
|
||||
desc: "multiple hosts, no headers, no path",
|
||||
hosts: []string{"example.com", "foo.com"},
|
||||
want: "(Host(`example.com`) || Host(`foo.com`))",
|
||||
},
|
||||
{
|
||||
desc: "single host, single header, no path",
|
||||
hosts: []string{"example.com"},
|
||||
headers: map[string]knativenetworkingv1alpha1.HeaderMatch{
|
||||
"X-Header": {Exact: "value"},
|
||||
},
|
||||
want: "(Host(`example.com`)) && (Header(`X-Header`,`value`))",
|
||||
},
|
||||
{
|
||||
desc: "single host, multiple headers, no path",
|
||||
hosts: []string{"example.com"},
|
||||
headers: map[string]knativenetworkingv1alpha1.HeaderMatch{
|
||||
"X-Header": {Exact: "value"},
|
||||
"X-Header2": {Exact: "value2"},
|
||||
},
|
||||
want: "(Host(`example.com`)) && (Header(`X-Header`,`value`) && Header(`X-Header2`,`value2`))",
|
||||
},
|
||||
{
|
||||
desc: "single host, multiple headers, with path",
|
||||
hosts: []string{"example.com"},
|
||||
headers: map[string]knativenetworkingv1alpha1.HeaderMatch{
|
||||
"X-Header": {Exact: "value"},
|
||||
"X-Header2": {Exact: "value2"},
|
||||
},
|
||||
path: "/foo",
|
||||
want: "(Host(`example.com`)) && (Header(`X-Header`,`value`) && Header(`X-Header2`,`value2`)) && PathPrefix(`/foo`)",
|
||||
},
|
||||
{
|
||||
desc: "single host, no headers, with path",
|
||||
hosts: []string{"example.com"},
|
||||
path: "/foo",
|
||||
want: "(Host(`example.com`)) && PathPrefix(`/foo`)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := buildRule(test.hosts, test.headers, test.path)
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mergeHTTPConfigs(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
configs []*dynamic.HTTPConfiguration
|
||||
want *dynamic.HTTPConfiguration
|
||||
}{
|
||||
{
|
||||
desc: "one empty configuration",
|
||||
configs: []*dynamic.HTTPConfiguration{
|
||||
{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"router1": {Rule: "Host(`example.com`)"},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{},
|
||||
},
|
||||
},
|
||||
want: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"router1": {Rule: "Host(`example.com`)"},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "merging two non-empty configurations",
|
||||
configs: []*dynamic.HTTPConfiguration{
|
||||
{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"router1": {Rule: "Host(`example.com`)"},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"router2": {Rule: "PathPrefix(`/test`)"},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"middleware2": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"service2": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"router1": {Rule: "Host(`example.com`)"},
|
||||
"router2": {Rule: "PathPrefix(`/test`)"},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"middleware1": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
"middleware2": {Headers: &dynamic.Headers{CustomRequestHeaders: map[string]string{"X-Test": "value"}}},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"service1": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
"service2": {LoadBalancer: &dynamic.ServersLoadBalancer{Servers: []dynamic.Server{{URL: "http://example.com"}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := mergeHTTPConfigs(test.configs...)
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readResources(t *testing.T, paths []string) ([]runtime.Object, []runtime.Object) {
|
||||
t.Helper()
|
||||
|
||||
var (
|
||||
k8sObjects []runtime.Object
|
||||
knObjects []runtime.Object
|
||||
)
|
||||
for _, path := range paths {
|
||||
yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
objects := k8s.MustParseYaml(yamlContent)
|
||||
for _, obj := range objects {
|
||||
switch obj.GetObjectKind().GroupVersionKind().Group {
|
||||
case "networking.internal.knative.dev":
|
||||
knObjects = append(knObjects, obj)
|
||||
default:
|
||||
k8sObjects = append(k8sObjects, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return k8sObjects, knObjects
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue