1766 lines
46 KiB
Go
1766 lines
46 KiB
Go
/*
|
|
Copyright 2018 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 controller
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/eapache/channels"
|
|
corev1 "k8s.io/api/core/v1"
|
|
v1 "k8s.io/api/core/v1"
|
|
networking "k8s.io/api/networking/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
"k8s.io/ingress-nginx/internal/file"
|
|
"k8s.io/ingress-nginx/internal/ingress"
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations"
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
|
|
"k8s.io/ingress-nginx/internal/ingress/controller/config"
|
|
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
|
"k8s.io/ingress-nginx/internal/ingress/controller/store"
|
|
"k8s.io/ingress-nginx/internal/ingress/defaults"
|
|
"k8s.io/ingress-nginx/internal/ingress/metric"
|
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
|
"k8s.io/ingress-nginx/internal/k8s"
|
|
"k8s.io/ingress-nginx/internal/net/ssl"
|
|
)
|
|
|
|
type fakeIngressStore struct {
|
|
ingresses []*ingress.Ingress
|
|
}
|
|
|
|
func (fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
|
|
return ngx_config.Configuration{}
|
|
}
|
|
|
|
func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fakeIngressStore) GetSecret(key string) (*corev1.Secret, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fakeIngressStore) GetService(key string) (*corev1.Service, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fakeIngressStore) GetServiceEndpoints(key string) (*corev1.Endpoints, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fis fakeIngressStore) ListIngresses() []*ingress.Ingress {
|
|
return fis.ingresses
|
|
}
|
|
|
|
func (fis fakeIngressStore) FilterIngresses(ingresses []*ingress.Ingress, filterFunc store.IngressFilterFunc) []*ingress.Ingress {
|
|
return ingresses
|
|
}
|
|
|
|
func (fakeIngressStore) GetLocalSSLCert(name string) (*ingress.SSLCert, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fakeIngressStore) ListLocalSSLCerts() []*ingress.SSLCert {
|
|
return nil
|
|
}
|
|
|
|
func (fakeIngressStore) GetAuthCertificate(string) (*resolver.AuthSSLCert, error) {
|
|
return nil, fmt.Errorf("test error")
|
|
}
|
|
|
|
func (fakeIngressStore) GetDefaultBackend() defaults.Backend {
|
|
return defaults.Backend{}
|
|
}
|
|
|
|
func (fakeIngressStore) Run(stopCh chan struct{}) {}
|
|
|
|
type testNginxTestCommand struct {
|
|
t *testing.T
|
|
expected string
|
|
out []byte
|
|
err error
|
|
}
|
|
|
|
func (ntc testNginxTestCommand) ExecCommand(args ...string) *exec.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (ntc testNginxTestCommand) Test(cfg string) ([]byte, error) {
|
|
fd, err := os.Open(cfg)
|
|
if err != nil {
|
|
ntc.t.Errorf("could not read generated nginx configuration: %v", err.Error())
|
|
return nil, err
|
|
}
|
|
defer fd.Close()
|
|
bytes, err := ioutil.ReadAll(fd)
|
|
if err != nil {
|
|
ntc.t.Errorf("could not read generated nginx configuration: %v", err.Error())
|
|
}
|
|
if string(bytes) != ntc.expected {
|
|
ntc.t.Errorf("unexpected generated configuration %v. Expecting %v", string(bytes), ntc.expected)
|
|
}
|
|
return ntc.out, ntc.err
|
|
}
|
|
|
|
type fakeTemplate struct{}
|
|
|
|
func (fakeTemplate) Write(conf config.TemplateConfig) ([]byte, error) {
|
|
r := []byte{}
|
|
for _, s := range conf.Servers {
|
|
if len(r) > 0 {
|
|
r = append(r, ',')
|
|
}
|
|
r = append(r, []byte(s.Hostname)...)
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func TestCheckIngress(t *testing.T) {
|
|
defer func() {
|
|
filepath.Walk(os.TempDir(), func(path string, info os.FileInfo, err error) error {
|
|
if info.IsDir() && os.TempDir() != path {
|
|
return filepath.SkipDir
|
|
}
|
|
if strings.HasPrefix(info.Name(), tempNginxPattern) {
|
|
os.Remove(path)
|
|
}
|
|
return nil
|
|
})
|
|
}()
|
|
|
|
err := file.CreateRequiredDirectories()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Ensure no panic with wrong arguments
|
|
var nginx *NGINXController
|
|
nginx.CheckIngress(nil)
|
|
nginx = newNGINXController(t)
|
|
nginx.CheckIngress(nil)
|
|
nginx.metricCollector = metric.DummyCollector{}
|
|
|
|
nginx.t = fakeTemplate{}
|
|
nginx.store = fakeIngressStore{
|
|
ingresses: []*ingress.Ingress{},
|
|
}
|
|
|
|
ing := &networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-ingress",
|
|
Namespace: "user-namespace",
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("When the ingress class differs from nginx", func(t *testing.T) {
|
|
ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "different"
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: fmt.Errorf("test error"),
|
|
}
|
|
if nginx.CheckIngress(ing) != nil {
|
|
t.Errorf("with a different ingress class, no error should be returned")
|
|
}
|
|
})
|
|
|
|
t.Run("when the class is the nginx one", func(t *testing.T) {
|
|
ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx"
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: nil,
|
|
expected: "_,example.com",
|
|
}
|
|
if nginx.CheckIngress(ing) != nil {
|
|
t.Errorf("with a new ingress without error, no error should be returned")
|
|
}
|
|
|
|
t.Run("When the hostname is updated", func(t *testing.T) {
|
|
nginx.store = fakeIngressStore{
|
|
ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: *ing,
|
|
ParsedAnnotations: &annotations.Ingress{},
|
|
},
|
|
},
|
|
}
|
|
ing.Spec.Rules[0].Host = "test.example.com"
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: nil,
|
|
expected: "_,test.example.com",
|
|
}
|
|
if nginx.CheckIngress(ing) != nil {
|
|
t.Errorf("with a new ingress without error, no error should be returned")
|
|
}
|
|
})
|
|
|
|
t.Run("When nginx test returns an error", func(t *testing.T) {
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: fmt.Errorf("test error"),
|
|
out: []byte("this is the test command output"),
|
|
expected: "_,test.example.com",
|
|
}
|
|
if nginx.CheckIngress(ing) == nil {
|
|
t.Errorf("with a new ingress with an error, an error should be returned")
|
|
}
|
|
})
|
|
|
|
t.Run("When the default annotation prefix is used despite an override", func(t *testing.T) {
|
|
parser.AnnotationsPrefix = "ingress.kubernetes.io"
|
|
ing.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/backend-protocol"] = "GRPC"
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: nil,
|
|
}
|
|
if nginx.CheckIngress(ing) == nil {
|
|
t.Errorf("with a custom annotation prefix, ingresses using the default should be rejected")
|
|
}
|
|
})
|
|
|
|
t.Run("When the ingress is in a different namespace than the watched one", func(t *testing.T) {
|
|
nginx.command = testNginxTestCommand{
|
|
t: t,
|
|
err: fmt.Errorf("test error"),
|
|
}
|
|
nginx.cfg.Namespace = "other-namespace"
|
|
ing.ObjectMeta.Namespace = "test-namespace"
|
|
if nginx.CheckIngress(ing) != nil {
|
|
t.Errorf("with a new ingress without error, no error should be returned")
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestMergeAlternativeBackends(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
ingress *ingress.Ingress
|
|
upstreams map[string]*ingress.Backend
|
|
servers map[string]*ingress.Server
|
|
expUpstreams map[string]*ingress.Backend
|
|
expServers map[string]*ingress.Server
|
|
}{
|
|
"alternative backend has no server and embeds into matching real backend": {
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"example.com": {
|
|
Hostname: "example.com",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
AlternativeBackends: []string{"example-http-svc-canary-80"},
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"example.com": {
|
|
Hostname: "example.com",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"alternative backend merges with the correct real backend when multiple are present": {
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "foo.bar",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "foo-http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-foo-http-svc-80": {
|
|
Name: "example-foo-http-svc-80",
|
|
NoServer: false,
|
|
},
|
|
"example-foo-http-svc-canary-80": {
|
|
Name: "example-foo-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
AlternativeBackends: []string{"example-http-svc-canary-80"},
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"foo.bar": {
|
|
Hostname: "foo.bar",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-foo-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
"example.com": {
|
|
Hostname: "example.com",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-foo-http-svc-80": {
|
|
Name: "example-foo-http-svc-80",
|
|
NoServer: false,
|
|
AlternativeBackends: []string{"example-foo-http-svc-canary-80"},
|
|
},
|
|
"example-foo-http-svc-canary-80": {
|
|
Name: "example-foo-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
AlternativeBackends: []string{"example-http-svc-canary-80"},
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"example.com": {
|
|
Hostname: "example.com",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"alternative backend does not merge into itself": {
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{},
|
|
map[string]*ingress.Backend{},
|
|
map[string]*ingress.Server{},
|
|
},
|
|
"catch-all alternative backend has no server and embeds into matching real backend": {
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Backend: &networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"_": {
|
|
Hostname: "_",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"example-http-svc-80": {
|
|
Name: "example-http-svc-80",
|
|
NoServer: false,
|
|
AlternativeBackends: []string{"example-http-svc-canary-80"},
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"_": {
|
|
Hostname: "_",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "example-http-svc-80",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"catch-all alternative backend does not merge into itself": {
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Backend: &networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{
|
|
"upstream-default-backend": {
|
|
Name: "upstream-default-backend",
|
|
NoServer: false,
|
|
},
|
|
"example-http-svc-canary-80": {
|
|
Name: "example-http-svc-canary-80",
|
|
NoServer: true,
|
|
TrafficShapingPolicy: ingress.TrafficShapingPolicy{
|
|
Weight: 20,
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Server{
|
|
"_": {
|
|
Hostname: "_",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "upstream-default-backend",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
map[string]*ingress.Backend{},
|
|
map[string]*ingress.Server{
|
|
"_": {
|
|
Hostname: "_",
|
|
Locations: []*ingress.Location{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: "upstream-default-backend",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for title, tc := range testCases {
|
|
t.Run(title, func(t *testing.T) {
|
|
mergeAlternativeBackends(tc.ingress, tc.upstreams, tc.servers)
|
|
|
|
for upsName, expUpstream := range tc.expUpstreams {
|
|
actualUpstream, ok := tc.upstreams[upsName]
|
|
if !ok {
|
|
t.Errorf("expected upstream %s to exist but it did not", upsName)
|
|
}
|
|
|
|
if !actualUpstream.Equal(expUpstream) {
|
|
t.Logf("actual upstream %s alternative backends: %s", actualUpstream.Name, actualUpstream.AlternativeBackends)
|
|
t.Logf("expected upstream %s alternative backends: %s", expUpstream.Name, expUpstream.AlternativeBackends)
|
|
t.Errorf("upstream %s was not equal to what was expected: ", upsName)
|
|
}
|
|
}
|
|
|
|
for serverName, expServer := range tc.expServers {
|
|
actualServer, ok := tc.servers[serverName]
|
|
if !ok {
|
|
t.Errorf("expected server %s to exist but it did not", serverName)
|
|
}
|
|
|
|
if !actualServer.Equal(expServer) {
|
|
t.Errorf("server %s was not equal to what was expected", serverName)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractTLSSecretName(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
host string
|
|
ingress *ingress.Ingress
|
|
fn func(string) (*ingress.SSLCert, error)
|
|
expName string
|
|
}{
|
|
"nil ingress": {
|
|
"foo.bar",
|
|
nil,
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return nil, nil
|
|
},
|
|
"",
|
|
},
|
|
"empty ingress": {
|
|
"foo.bar",
|
|
&ingress.Ingress{},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return nil, nil
|
|
},
|
|
"",
|
|
},
|
|
"ingress tls, nil secret": {
|
|
"foo.bar",
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
TLS: []networking.IngressTLS{
|
|
{SecretName: "demo"},
|
|
},
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "foo.bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return nil, nil
|
|
},
|
|
"",
|
|
},
|
|
"ingress tls, no host, matching cert cn": {
|
|
"foo.bar",
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
TLS: []networking.IngressTLS{
|
|
{SecretName: "demo"},
|
|
},
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "foo.bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return &ingress.SSLCert{
|
|
Certificate: fakeX509Cert([]string{"foo.bar", "example.com"}),
|
|
}, nil
|
|
},
|
|
"demo",
|
|
},
|
|
"ingress tls, no host, wildcard cert with matching cn": {
|
|
"foo.bar",
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
TLS: []networking.IngressTLS{
|
|
{
|
|
SecretName: "demo",
|
|
},
|
|
},
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "test.foo.bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return &ingress.SSLCert{
|
|
Certificate: fakeX509Cert([]string{"*.foo.bar", "foo.bar"}),
|
|
}, nil
|
|
},
|
|
"demo",
|
|
},
|
|
"ingress tls, hosts, matching cert cn": {
|
|
"foo.bar",
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
TLS: []networking.IngressTLS{
|
|
{
|
|
Hosts: []string{"foo.bar", "example.com"},
|
|
SecretName: "demo",
|
|
},
|
|
},
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "foo.bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return nil, nil
|
|
},
|
|
"demo",
|
|
},
|
|
"ingress tls, hosts, matching cert cn, uppercase host": {
|
|
"FOO.BAR",
|
|
&ingress.Ingress{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
TLS: []networking.IngressTLS{
|
|
{
|
|
Hosts: []string{"foo.bar", "example.com"},
|
|
SecretName: "demo",
|
|
},
|
|
},
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "foo.bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
func(string) (*ingress.SSLCert, error) {
|
|
return nil, nil
|
|
},
|
|
"demo",
|
|
},
|
|
}
|
|
|
|
for title, tc := range testCases {
|
|
t.Run(title, func(t *testing.T) {
|
|
name := extractTLSSecretName(tc.host, tc.ingress, tc.fn)
|
|
if name != tc.expName {
|
|
t.Errorf("Expected Secret name %q (got %q)", tc.expName, name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetBackendServers(t *testing.T) {
|
|
|
|
testCases := []struct {
|
|
Ingresses []*ingress.Ingress
|
|
Validate func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server)
|
|
SetConfigMap func(namespace string) *v1.ConfigMap
|
|
}{
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Backend: &networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 1 {
|
|
t.Errorf("servers count should be 1, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[0]
|
|
if s.Hostname != "_" {
|
|
t.Errorf("server hostname should be '_', got '%s'", s.Hostname)
|
|
}
|
|
if !s.Locations[0].IsDefBackend {
|
|
t.Errorf("server location 0 should be default backend")
|
|
}
|
|
|
|
if s.Locations[0].Backend != defUpstreamName {
|
|
t.Errorf("location backend should be '%s', got '%s'", defUpstreamName, s.Locations[0].Backend)
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Backend: &networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Backend: &networking.IngressBackend{
|
|
ServiceName: "http-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 1 {
|
|
t.Errorf("servers count should be 1, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[0]
|
|
if s.Hostname != "_" {
|
|
t.Errorf("server hostname should be '_', got '%s'", s.Hostname)
|
|
}
|
|
if s.Locations[0].IsDefBackend {
|
|
t.Errorf("server location 0 should not be default backend")
|
|
}
|
|
|
|
if s.Locations[0].Backend != "example-http-svc-80" {
|
|
t.Errorf("location backend should be 'example-http-svc-80', got '%s'", s.Locations[0].Backend)
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 1 {
|
|
t.Errorf("servers count should be 1, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[0]
|
|
if s.Hostname != "_" {
|
|
t.Errorf("server hostname should be '_', got '%s'", s.Hostname)
|
|
}
|
|
if !s.Locations[0].IsDefBackend {
|
|
t.Errorf("server location 0 should be default backend")
|
|
}
|
|
|
|
if s.Locations[0].Backend != defUpstreamName {
|
|
t.Errorf("location backend should be '%s', got '%s'", defUpstreamName, s.Locations[0].Backend)
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-canary",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-canary",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 2 {
|
|
t.Errorf("servers count should be 2, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[0]
|
|
if s.Hostname != "_" {
|
|
t.Errorf("server hostname should be '_', got '%s'", s.Hostname)
|
|
}
|
|
if !s.Locations[0].IsDefBackend {
|
|
t.Errorf("server location 0 should be default backend")
|
|
}
|
|
|
|
if s.Locations[0].Backend != defUpstreamName {
|
|
t.Errorf("location backend should be '%s', got '%s'", defUpstreamName, s.Locations[0].Backend)
|
|
}
|
|
|
|
s = servers[1]
|
|
if s.Hostname != "example.com" {
|
|
t.Errorf("server hostname should be 'example.com', got '%s'", s.Hostname)
|
|
}
|
|
|
|
if s.Locations[0].Backend != "example-http-svc-80" {
|
|
t.Errorf("location backend should be 'example-http-svc-80', got '%s'", s.Locations[0].Backend)
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-a",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/a",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-1",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-a-canary",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/a",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-2",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-b",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/b",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-2",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-b-canary",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/b",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-1",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-c",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/c",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-1",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: false,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "example-c-canary",
|
|
Namespace: "example",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/c",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "http-svc-2",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
Canary: canary.Config{
|
|
Enabled: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 2 {
|
|
t.Errorf("servers count should be 2, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[0]
|
|
if s.Hostname != "_" {
|
|
t.Errorf("server hostname should be '_', got '%s'", s.Hostname)
|
|
}
|
|
if !s.Locations[0].IsDefBackend {
|
|
t.Errorf("server location 0 should be default backend")
|
|
}
|
|
|
|
if s.Locations[0].Backend != defUpstreamName {
|
|
t.Errorf("location backend should be '%s', got '%s'", defUpstreamName, s.Locations[0].Backend)
|
|
}
|
|
|
|
s = servers[1]
|
|
if s.Hostname != "example.com" {
|
|
t.Errorf("server hostname should be 'example.com', got '%s'", s.Hostname)
|
|
}
|
|
|
|
if s.Locations[0].Backend != "example-http-svc-1-80" || s.Locations[1].Backend != "example-http-svc-1-80" || s.Locations[2].Backend != "example-http-svc-1-80" {
|
|
t.Errorf("all location backend should be 'example-http-svc-1-80'")
|
|
}
|
|
|
|
if len(upstreams) != 3 {
|
|
t.Errorf("upstreams count should be 3, got %d", len(upstreams))
|
|
return
|
|
}
|
|
|
|
if upstreams[0].Name != "example-http-svc-1-80" {
|
|
t.Errorf("example-http-svc-1-80 should be first upstream, got %s", upstreams[0].Name)
|
|
return
|
|
}
|
|
if upstreams[0].NoServer {
|
|
t.Errorf("'example-http-svc-1-80' should be primary upstream, got as alternative upstream")
|
|
}
|
|
if len(upstreams[0].AlternativeBackends) != 1 || upstreams[0].AlternativeBackends[0] != "example-http-svc-2-80" {
|
|
t.Errorf("example-http-svc-2-80 should be alternative upstream for 'example-http-svc-1-80'")
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "proxy-ssl-1",
|
|
Namespace: "proxyssl",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/path1",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "path1-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
ProxySSL: proxyssl.Config{
|
|
AuthSSLCert: resolver.AuthSSLCert{
|
|
CAFileName: "cafile1.crt",
|
|
Secret: "secret1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "proxy-ssl-2",
|
|
Namespace: "proxyssl",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/path2",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "path2-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
ProxySSL: proxyssl.Config{
|
|
AuthSSLCert: resolver.AuthSSLCert{
|
|
CAFileName: "cafile1.crt",
|
|
Secret: "secret1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 2 {
|
|
t.Errorf("servers count should be 2, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[1]
|
|
|
|
if s.ProxySSL.CAFileName != ingresses[0].ParsedAnnotations.ProxySSL.CAFileName {
|
|
t.Errorf("server cafilename should be '%s', got '%s'", ingresses[0].ParsedAnnotations.ProxySSL.CAFileName, s.ProxySSL.CAFileName)
|
|
}
|
|
|
|
if s.Locations[0].ProxySSL.CAFileName != ingresses[0].ParsedAnnotations.ProxySSL.CAFileName {
|
|
t.Errorf("location cafilename should be '%s', got '%s'", ingresses[0].ParsedAnnotations.ProxySSL.CAFileName, s.Locations[0].ProxySSL.CAFileName)
|
|
}
|
|
|
|
if s.Locations[1].ProxySSL.CAFileName != ingresses[1].ParsedAnnotations.ProxySSL.CAFileName {
|
|
t.Errorf("location cafilename should be '%s', got '%s'", ingresses[1].ParsedAnnotations.ProxySSL.CAFileName, s.Locations[0].ProxySSL.CAFileName)
|
|
}
|
|
},
|
|
SetConfigMap: testConfigMap,
|
|
},
|
|
{
|
|
Ingresses: []*ingress.Ingress{
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "proxy-ssl-1",
|
|
Namespace: "proxyssl",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/path1",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "path1-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
ProxySSL: proxyssl.Config{
|
|
AuthSSLCert: resolver.AuthSSLCert{
|
|
CAFileName: "cafile1.crt",
|
|
Secret: "secret1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Ingress: networking.Ingress{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "proxy-ssl-2",
|
|
Namespace: "proxyssl",
|
|
},
|
|
Spec: networking.IngressSpec{
|
|
Rules: []networking.IngressRule{
|
|
{
|
|
Host: "example.com",
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
Paths: []networking.HTTPIngressPath{
|
|
{
|
|
Path: "/path2",
|
|
PathType: &pathTypePrefix,
|
|
Backend: networking.IngressBackend{
|
|
ServiceName: "path2-svc",
|
|
ServicePort: intstr.IntOrString{
|
|
Type: intstr.Int,
|
|
IntVal: 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ParsedAnnotations: &annotations.Ingress{
|
|
ProxySSL: proxyssl.Config{
|
|
AuthSSLCert: resolver.AuthSSLCert{
|
|
CAFileName: "cafile1.crt",
|
|
Secret: "secret1",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
|
|
if len(servers) != 2 {
|
|
t.Errorf("servers count should be 2, got %d", len(servers))
|
|
return
|
|
}
|
|
|
|
s := servers[1]
|
|
|
|
if s.ProxySSL.CAFileName != "" {
|
|
t.Errorf("server cafilename should be empty, got '%s'", s.ProxySSL.CAFileName)
|
|
}
|
|
|
|
if s.Locations[0].ProxySSL.CAFileName != ingresses[0].ParsedAnnotations.ProxySSL.CAFileName {
|
|
t.Errorf("location cafilename should be '%s', got '%s'", ingresses[0].ParsedAnnotations.ProxySSL.CAFileName, s.Locations[0].ProxySSL.CAFileName)
|
|
}
|
|
|
|
if s.Locations[1].ProxySSL.CAFileName != ingresses[1].ParsedAnnotations.ProxySSL.CAFileName {
|
|
t.Errorf("location cafilename should be '%s', got '%s'", ingresses[1].ParsedAnnotations.ProxySSL.CAFileName, s.Locations[0].ProxySSL.CAFileName)
|
|
}
|
|
},
|
|
SetConfigMap: func(ns string) *v1.ConfigMap {
|
|
return &v1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "config",
|
|
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
|
|
},
|
|
Data: map[string]string{
|
|
"proxy-ssl-location-only": "true",
|
|
},
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
nginxController := newDynamicNginxController(t, testCase.SetConfigMap)
|
|
upstreams, servers := nginxController.getBackendServers(testCase.Ingresses)
|
|
testCase.Validate(testCase.Ingresses, upstreams, servers)
|
|
}
|
|
}
|
|
|
|
func testConfigMap(ns string) *v1.ConfigMap {
|
|
return &v1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "config",
|
|
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
|
|
},
|
|
}
|
|
}
|
|
|
|
func newNGINXController(t *testing.T) *NGINXController {
|
|
ns := v1.NamespaceDefault
|
|
|
|
clientSet := fake.NewSimpleClientset()
|
|
|
|
configMap := &v1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "config",
|
|
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
|
|
},
|
|
}
|
|
|
|
_, err := clientSet.CoreV1().ConfigMaps(ns).Create(context.TODO(), configMap, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error creating the configuration map: %v", err)
|
|
}
|
|
|
|
k8s.IngressPodDetails = &k8s.PodInfo{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "testpod",
|
|
Namespace: ns,
|
|
Labels: map[string]string{
|
|
"pod-template-hash": "1234",
|
|
},
|
|
},
|
|
}
|
|
|
|
storer := store.New(
|
|
ns,
|
|
fmt.Sprintf("%v/config", ns),
|
|
fmt.Sprintf("%v/tcp", ns),
|
|
fmt.Sprintf("%v/udp", ns),
|
|
"",
|
|
10*time.Minute,
|
|
clientSet,
|
|
channels.NewRingChannel(10),
|
|
false)
|
|
|
|
sslCert := ssl.GetFakeSSLCert()
|
|
config := &Configuration{
|
|
FakeCertificate: sslCert,
|
|
ListenPorts: &ngx_config.ListenPorts{
|
|
Default: 80,
|
|
},
|
|
}
|
|
|
|
return &NGINXController{
|
|
store: storer,
|
|
cfg: config,
|
|
command: NewNginxCommand(),
|
|
}
|
|
}
|
|
|
|
var oidExtensionSubjectAltName = asn1.ObjectIdentifier{2, 5, 29, 17}
|
|
|
|
func fakeX509Cert(dnsNames []string) *x509.Certificate {
|
|
return &x509.Certificate{
|
|
DNSNames: dnsNames,
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newDynamicNginxController(t *testing.T, setConfigMap func(string) *v1.ConfigMap) *NGINXController {
|
|
ns := v1.NamespaceDefault
|
|
|
|
clientSet := fake.NewSimpleClientset()
|
|
configMap := setConfigMap(ns)
|
|
|
|
_, err := clientSet.CoreV1().ConfigMaps(ns).Create(context.TODO(), configMap, metav1.CreateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("error creating the configuration map: %v", err)
|
|
}
|
|
|
|
k8s.IngressPodDetails = &k8s.PodInfo{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "testpod",
|
|
Namespace: ns,
|
|
Labels: map[string]string{
|
|
"pod-template-hash": "1234",
|
|
},
|
|
},
|
|
}
|
|
|
|
storer := store.New(
|
|
ns,
|
|
fmt.Sprintf("%v/config", ns),
|
|
fmt.Sprintf("%v/tcp", ns),
|
|
fmt.Sprintf("%v/udp", ns),
|
|
"",
|
|
10*time.Minute,
|
|
clientSet,
|
|
channels.NewRingChannel(10),
|
|
false)
|
|
|
|
sslCert := ssl.GetFakeSSLCert()
|
|
config := &Configuration{
|
|
FakeCertificate: sslCert,
|
|
ListenPorts: &ngx_config.ListenPorts{
|
|
Default: 80,
|
|
},
|
|
}
|
|
|
|
return &NGINXController{
|
|
store: storer,
|
|
cfg: config,
|
|
command: NewNginxCommand(),
|
|
}
|
|
}
|