98 lines
4.2 KiB
Go
98 lines
4.2 KiB
Go
package topology
|
|
|
|
import (
|
|
"testing"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
networkingv1 "k8s.io/api/networking/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
func ptr[T any](v T) *T { return &v }
|
|
|
|
func path(p string, pt networkingv1.PathType, svc string) networkingv1.HTTPIngressPath {
|
|
return networkingv1.HTTPIngressPath{Path: p, PathType: &pt, Backend: networkingv1.IngressBackend{
|
|
Service: &networkingv1.IngressServiceBackend{Name: svc, Port: networkingv1.ServiceBackendPort{Number: 80}}}}
|
|
}
|
|
|
|
func fixture() Snapshot {
|
|
ing := networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{Namespace: "shop", Name: "public"}, Spec: networkingv1.IngressSpec{Rules: []networkingv1.IngressRule{
|
|
{Host: "shop.example.com", IngressRuleValue: networkingv1.IngressRuleValue{HTTP: &networkingv1.HTTPIngressRuleValue{Paths: []networkingv1.HTTPIngressPath{
|
|
path("/", networkingv1.PathTypePrefix, "web"),
|
|
path("/api", networkingv1.PathTypePrefix, "api"),
|
|
path("/legacy", networkingv1.PathTypeExact, "old"),
|
|
path("/pay", networkingv1.PathTypePrefix, "gone"),
|
|
path("/cdn", networkingv1.PathTypePrefix, "cdn"),
|
|
}}}},
|
|
{Host: "*.example.com", IngressRuleValue: networkingv1.IngressRuleValue{HTTP: &networkingv1.HTTPIngressRuleValue{Paths: []networkingv1.HTTPIngressPath{
|
|
path("/", networkingv1.PathTypePrefix, "web"),
|
|
}}}},
|
|
}}}
|
|
svcs := []corev1.Service{
|
|
{ObjectMeta: metav1.ObjectMeta{Namespace: "shop", Name: "web"}, Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "web"}}},
|
|
{ObjectMeta: metav1.ObjectMeta{Namespace: "shop", Name: "api"}, Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "api"}}},
|
|
{ObjectMeta: metav1.ObjectMeta{Namespace: "shop", Name: "old"}, Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "old"}}},
|
|
{ObjectMeta: metav1.ObjectMeta{Namespace: "shop", Name: "cdn"}, Spec: corev1.ServiceSpec{Type: corev1.ServiceTypeExternalName, ExternalName: "cdn.vendor.net"}},
|
|
}
|
|
dep := func(ns, name, app string) appsv1.Deployment {
|
|
return appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: ns, Name: name}, Spec: appsv1.DeploymentSpec{
|
|
Template: corev1.PodTemplateSpec{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": app, "tier": "x"}}}}}
|
|
}
|
|
deps := []appsv1.Deployment{dep("shop", "web-v2", "web"), dep("shop", "api", "api"), dep("other", "web-other", "web"), dep("shop", "old", "old")}
|
|
return Build([]networkingv1.Ingress{ing}, svcs, deps)
|
|
}
|
|
|
|
func one(t *testing.T, s Snapshot, host, path string) Target {
|
|
t.Helper()
|
|
got := s.Resolve(host, path)
|
|
if len(got) != 1 {
|
|
t.Fatalf("%s%s: %d targets: %+v", host, path, len(got), got)
|
|
}
|
|
return got[0]
|
|
}
|
|
|
|
func TestResolveLongestPathAndDeployments(t *testing.T) {
|
|
s := fixture()
|
|
if g := one(t, s, "shop.example.com", "/api/orders?x=1"); g.Service != "api" || len(g.Deployments) != 1 || g.Deployments[0] != "api" {
|
|
t.Fatalf("api: %+v", g)
|
|
}
|
|
// the selector matches the same-namespace deployment only, not other/web-other
|
|
if g := one(t, s, "shop.example.com", "/index"); g.Service != "web" || len(g.Deployments) != 1 || g.Deployments[0] != "web-v2" {
|
|
t.Fatalf("web: %+v", g)
|
|
}
|
|
// /apix must not match the /api prefix (segment-wise)
|
|
if g := one(t, s, "shop.example.com", "/apix"); g.Service != "web" {
|
|
t.Fatalf("apix: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestResolveHostsPortsAndExact(t *testing.T) {
|
|
s := fixture()
|
|
if g := one(t, s, "SHOP.example.com:443", "/"); g.Host != "shop.example.com" {
|
|
t.Fatalf("host case/port: %+v", g)
|
|
}
|
|
if g := one(t, s, "blog.example.com", "/x"); g.Host != "*.example.com" {
|
|
t.Fatalf("wildcard: %+v", g)
|
|
}
|
|
if got := s.Resolve("a.b.example.com", "/"); len(got) != 0 {
|
|
t.Fatalf("wildcard matched two labels: %+v", got)
|
|
}
|
|
if g := one(t, s, "shop.example.com", "/legacy"); g.Service != "old" {
|
|
t.Fatalf("exact: %+v", g)
|
|
}
|
|
if g := one(t, s, "shop.example.com", "/legacy/x"); g.Service != "web" {
|
|
t.Fatalf("exact matched a subpath: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestMissingAndExternalBackends(t *testing.T) {
|
|
s := fixture()
|
|
if g := one(t, s, "shop.example.com", "/pay"); !g.Missing || g.Service != "gone" {
|
|
t.Fatalf("missing: %+v", g)
|
|
}
|
|
if g := one(t, s, "shop.example.com", "/cdn/a.js"); g.External != "cdn.vendor.net" || len(g.Deployments) != 0 {
|
|
t.Fatalf("external: %+v", g)
|
|
}
|
|
}
|